@polderlabs/bizar 5.6.0-beta.7 → 5.6.0-beta.9
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/agent-browser-up.sh +16 -0
- package/cli/agent-browser-update.mjs +322 -0
- package/cli/agent-browser-update.test.mjs +86 -0
- package/cli/bin.mjs +52 -4
- package/cli/cli-commands-validation.test.mjs +106 -0
- package/cli/commands/clip.mjs +18 -1
- package/cli/commands/eval.mjs +19 -1
- package/cli/commands/lightrag.mjs +0 -1
- package/cli/commands/ocr.mjs +18 -1
- package/cli/commands/usage.mjs +4 -0
- package/cli/commands/util.mjs +99 -1
- package/cli/digest.mjs +1 -1
- package/cli/provision.mjs +38 -0
- package/install.sh +40 -3
- package/package.json +1 -1
- package/packages/sdk/package.json +1 -1
package/cli/agent-browser-up.sh
CHANGED
|
@@ -132,5 +132,21 @@ case "${1:-start}" in
|
|
|
132
132
|
status) ensure_binary; print_status ;;
|
|
133
133
|
doctor) ensure_binary; $AB_BIN doctor ;;
|
|
134
134
|
install) ensure_binary; ensure_chrome; $AB_BIN install ;;
|
|
135
|
+
help|--help|-h)
|
|
136
|
+
cat <<'AGENT_BROWSER_UP_HELP'
|
|
137
|
+
agent-browser-up -- start/stop the agent-browser daemon
|
|
138
|
+
|
|
139
|
+
Usage: cli/agent-browser-up.sh <command>
|
|
140
|
+
|
|
141
|
+
Commands:
|
|
142
|
+
start Start the daemon (idempotent)
|
|
143
|
+
stop Stop the daemon
|
|
144
|
+
restart Stop + start
|
|
145
|
+
status Print daemon status
|
|
146
|
+
doctor Run agent-browser doctor
|
|
147
|
+
install Install agent-browser + download Chrome
|
|
148
|
+
AGENT_BROWSER_UP_HELP
|
|
149
|
+
exit 0
|
|
150
|
+
;;
|
|
135
151
|
*) fail "unknown command: $1 (use start|stop|restart|status|doctor|install)" ;;
|
|
136
152
|
esac
|
|
@@ -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,8 @@
|
|
|
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,
|
|
16
|
+
* heads-up, bg, agent-browser, agent-browser-up, providers, deploy, plugin,
|
|
17
|
+
* marketplace, plan, digest, backup, restore, clip, ocr, voice, workspace, eval
|
|
17
18
|
*/
|
|
18
19
|
import chalk from 'chalk';
|
|
19
20
|
import { existsSync, readFileSync } from 'node:fs';
|
|
@@ -115,10 +116,18 @@ function showHelp() {
|
|
|
115
116
|
deploy One-click deploy to Vercel, Cloudflare, Fly.io, or Docker
|
|
116
117
|
plugin <subcommand> Manage marketplace plugins (search/install/config/invoke)
|
|
117
118
|
marketplace <subcommand> Browse and install plugins from the public marketplace
|
|
118
|
-
agent-browser
|
|
119
|
+
agent-browser Install / update / verify the agent-browser CLI
|
|
120
|
+
agent-browser-up Start Chromium for agent-browser (start/stop/status)
|
|
119
121
|
providers detect Auto-detect provider API keys from env + cline.json
|
|
120
122
|
clip <subcommand> Manage web clipper saved clips (list/delete/configure)
|
|
121
123
|
ocr <subcommand> OCR operations on images (list/process/configure)
|
|
124
|
+
digest Manage weekly digests (list/view/generate)
|
|
125
|
+
backup Create / list / verify / delete backups of BizarHarness state
|
|
126
|
+
restore Restore BizarHarness from a backup
|
|
127
|
+
voice Manage voice notes (via the dashboard's HTTP API)
|
|
128
|
+
workspace Manage workspaces (via the dashboard's HTTP API)
|
|
129
|
+
eval Evaluate AI agent outputs against golden fixtures
|
|
130
|
+
plan [v6.0.0+] Reserved for future plan management
|
|
122
131
|
|
|
123
132
|
Examples:
|
|
124
133
|
bizar install
|
|
@@ -188,8 +197,30 @@ async function main() {
|
|
|
188
197
|
}
|
|
189
198
|
|
|
190
199
|
if (isHelpRequest && !cmd.startsWith('-')) {
|
|
191
|
-
// Pass --help to the command
|
|
192
|
-
|
|
200
|
+
// Pass --help to the command. Commands dispatched through util.mjs
|
|
201
|
+
// (audit, init, export, doctor, backup, restore, etc.) don't have
|
|
202
|
+
// their own cli/commands/<name>.mjs — they all live in util.mjs.
|
|
203
|
+
const UTIL_COMMANDS = new Set([
|
|
204
|
+
'audit', 'init', 'export', 'test-gate', 'dev-link', 'dev-unlink',
|
|
205
|
+
'doctor', 'repair', 'heads-up', 'bg', 'digest', 'backup', 'restore',
|
|
206
|
+
'agent-browser', 'update', 'providers', 'plan',
|
|
207
|
+
]);
|
|
208
|
+
const UTIL_ALIASES = new Set(['dashboard', 'agent-browser-up']);
|
|
209
|
+
let mod;
|
|
210
|
+
if (UTIL_COMMANDS.has(cmd)) {
|
|
211
|
+
// util-based commands: audit, init, etc.
|
|
212
|
+
mod = await importCommand('util');
|
|
213
|
+
} else if (UTIL_ALIASES.has(cmd)) {
|
|
214
|
+
// util aliases: dashboard → dash, agent-browser-up → bash script
|
|
215
|
+
if (cmd === 'dashboard') {
|
|
216
|
+
mod = await importCommand('dash');
|
|
217
|
+
} else if (cmd === 'agent-browser-up') {
|
|
218
|
+
// Run via util.mjs's `agent-browser-up` case
|
|
219
|
+
mod = await importCommand('util');
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
mod = await importCommand(cmd);
|
|
223
|
+
}
|
|
193
224
|
if (mod && typeof mod.run === 'function') {
|
|
194
225
|
await mod.run(cmd, cmdArgs, true);
|
|
195
226
|
return;
|
|
@@ -429,6 +460,9 @@ async function main() {
|
|
|
429
460
|
case 'heads-up':
|
|
430
461
|
case 'bg':
|
|
431
462
|
case 'digest':
|
|
463
|
+
case 'backup':
|
|
464
|
+
case 'restore':
|
|
465
|
+
case 'agent-browser':
|
|
432
466
|
case 'agent-browser-up':
|
|
433
467
|
case 'providers':
|
|
434
468
|
case 'plan': {
|
|
@@ -449,6 +483,20 @@ async function main() {
|
|
|
449
483
|
break;
|
|
450
484
|
}
|
|
451
485
|
|
|
486
|
+
case 'voice':
|
|
487
|
+
case 'workspace':
|
|
488
|
+
case 'eval': {
|
|
489
|
+
// Each of these has its own module file in cli/commands/.
|
|
490
|
+
const mod = await importCommand(cmd);
|
|
491
|
+
if (!mod) {
|
|
492
|
+
console.error(chalk.red(` ✗ Could not load ${cmd} command module`));
|
|
493
|
+
process.exit(EXIT_ERROR);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
if (mod.run) await mod.run(cmd, cmdArgs, isHelpRequest);
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
|
|
452
500
|
default: {
|
|
453
501
|
console.error(chalk.red(` ✗ Unknown command: ${cmd}`));
|
|
454
502
|
showHelp();
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* /tmp/test-cli-commands.mjs
|
|
4
|
+
* Comprehensive validation of every `bizar` CLI command.
|
|
5
|
+
*
|
|
6
|
+
* Tests:
|
|
7
|
+
* - All case branches in cli/bin.mjs respond to --help
|
|
8
|
+
* - All case branches in cli/commands/util.mjs respond to --help
|
|
9
|
+
* - Module exports are loadable
|
|
10
|
+
*
|
|
11
|
+
* Usage: node /tmp/test-cli-commands.mjs
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { spawnSync } from 'node:child_process';
|
|
15
|
+
|
|
16
|
+
const REPO = process.cwd();
|
|
17
|
+
const BIN = `${REPO}/cli/bin.mjs`;
|
|
18
|
+
|
|
19
|
+
const ALL_COMMANDS = [
|
|
20
|
+
// Util-based (live in cli/commands/util.mjs)
|
|
21
|
+
'audit', 'init', 'export', 'test-gate', 'dev-link', 'dev-unlink',
|
|
22
|
+
'doctor', 'repair', 'heads-up', 'bg', 'digest', 'backup', 'restore',
|
|
23
|
+
'agent-browser', 'agent-browser-up', 'providers',
|
|
24
|
+
// Own module
|
|
25
|
+
'install', 'update',
|
|
26
|
+
'service',
|
|
27
|
+
'dash', 'dashboard',
|
|
28
|
+
'minimax', 'tailscale', 'headroom', 'lightrag',
|
|
29
|
+
'mod', 'usage',
|
|
30
|
+
'deploy', 'plugin', 'marketplace',
|
|
31
|
+
'artifact',
|
|
32
|
+
'memory',
|
|
33
|
+
'memory',
|
|
34
|
+
'clip', 'ocr',
|
|
35
|
+
'voice', 'workspace', 'eval',
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
const NON_OWN_MODULE = new Set([
|
|
39
|
+
'audit', 'init', 'export', 'test-gate', 'dev-link', 'dev-unlink',
|
|
40
|
+
'doctor', 'repair', 'heads-up', 'bg', 'digest', 'backup', 'restore',
|
|
41
|
+
'agent-browser', 'agent-browser-up', 'providers', 'install', 'update',
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
const seen = new Set();
|
|
45
|
+
const passed = [];
|
|
46
|
+
const failed = [];
|
|
47
|
+
|
|
48
|
+
for (const cmd of ALL_COMMANDS) {
|
|
49
|
+
if (seen.has(cmd)) continue;
|
|
50
|
+
seen.add(cmd);
|
|
51
|
+
|
|
52
|
+
// Run `node cli/bin.mjs <cmd> --help` and check exit code + that it prints help
|
|
53
|
+
const r = spawnSync('node', [BIN, cmd, '--help'], {
|
|
54
|
+
timeout: 15_000,
|
|
55
|
+
encoding: 'utf8',
|
|
56
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
57
|
+
});
|
|
58
|
+
const stdout = r.stdout || '';
|
|
59
|
+
const stderr = r.stderr || '';
|
|
60
|
+
const exitCode = r.status;
|
|
61
|
+
|
|
62
|
+
// Pass criteria:
|
|
63
|
+
// - Exit code 0 OR 2 (CLINE exit code for usage/help is 2 sometimes)
|
|
64
|
+
// - stdout mentions the command name or contains a recognizable help string
|
|
65
|
+
// - stderr is empty OR mentions the command name (not "module not found")
|
|
66
|
+
const hasModuleError = stderr.includes('Cannot find module') ||
|
|
67
|
+
stderr.includes('Failed to load command module');
|
|
68
|
+
const hasHelp = stdout.toLowerCase().includes(cmd) ||
|
|
69
|
+
stdout.includes('Usage:') ||
|
|
70
|
+
stdout.includes('Subcommands:') ||
|
|
71
|
+
stdout.includes('Description:');
|
|
72
|
+
|
|
73
|
+
if ((exitCode === 0 || exitCode === 2) && !hasModuleError && (hasHelp || stdout.length > 50)) {
|
|
74
|
+
passed.push({ cmd, exitCode, stdoutLines: stdout.split('\n').length });
|
|
75
|
+
} else {
|
|
76
|
+
failed.push({ cmd, exitCode, hasModuleError, hasHelp, stdout: stdout.slice(0, 200), stderr: stderr.slice(0, 200) });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
console.log(`\n=== CLI command validation ===\n`);
|
|
81
|
+
console.log(`Passed: ${passed.length}/${seen.size}`);
|
|
82
|
+
console.log(`Failed: ${failed.length}\n`);
|
|
83
|
+
|
|
84
|
+
if (passed.length > 0) {
|
|
85
|
+
console.log('=== Passing commands ===');
|
|
86
|
+
for (const p of passed) {
|
|
87
|
+
console.log(` ✓ ${p.cmd.padEnd(20)} (exit ${p.exitCode}, ${p.stdoutLines} lines)`);
|
|
88
|
+
}
|
|
89
|
+
console.log();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (failed.length > 0) {
|
|
93
|
+
console.log('=== FAILING commands ===');
|
|
94
|
+
for (const f of failed) {
|
|
95
|
+
console.log(` ✗ ${f.cmd.padEnd(20)} exitCode=${f.exitCode} moduleErr=${f.hasModuleError} hasHelp=${f.hasHelp}`);
|
|
96
|
+
if (f.hasModuleError) {
|
|
97
|
+
console.log(` stderr: ${f.stderr.trim()}`);
|
|
98
|
+
} else if (f.exitCode !== 0 && f.exitCode !== 2) {
|
|
99
|
+
console.log(` stderr: ${f.stderr.trim().slice(0, 200)}`);
|
|
100
|
+
console.log(` stdout: ${f.stdout.trim().slice(0, 200)}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
console.log();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
process.exit(failed.length > 0 ? 1 : 0);
|
package/cli/commands/clip.mjs
CHANGED
|
@@ -10,7 +10,24 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import chalk from 'chalk';
|
|
13
|
-
|
|
13
|
+
// Mirror `readDashboardConn` from clip.mjs / minimax.mjs / usage.mjs.
|
|
14
|
+
function readDashboardConn() {
|
|
15
|
+
const cfgDir = process.env.XDG_CONFIG_HOME
|
|
16
|
+
? require_('node:path').join(process.env.XDG_CONFIG_HOME, 'bizar')
|
|
17
|
+
: require_('node:path').join(require_('node:os').homedir(), '.config', 'bizar');
|
|
18
|
+
const portPath = require_('node:path').join(cfgDir, 'dashboard.port');
|
|
19
|
+
const secretPath = require_('node:path').join(cfgDir, 'dashboard.secret');
|
|
20
|
+
const port = require_('node:fs').existsSync(portPath)
|
|
21
|
+
? parseInt(require_('node:fs').readFileSync(portPath, 'utf8').trim(), 10)
|
|
22
|
+
: 4321;
|
|
23
|
+
const secret = require_('node:fs').existsSync(secretPath)
|
|
24
|
+
? require_('node:fs').readFileSync(secretPath, 'utf8').trim()
|
|
25
|
+
: '';
|
|
26
|
+
return {
|
|
27
|
+
port: Number.isFinite(port) && port > 0 ? port : 4321,
|
|
28
|
+
secret,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
14
31
|
|
|
15
32
|
// ── API helpers ────────────────────────────────────────────────────────────────
|
|
16
33
|
|
package/cli/commands/eval.mjs
CHANGED
|
@@ -15,7 +15,25 @@ import chalk from 'chalk';
|
|
|
15
15
|
import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
16
16
|
import { join, dirname } from 'node:path';
|
|
17
17
|
import { homedir } from 'node:os';
|
|
18
|
-
|
|
18
|
+
|
|
19
|
+
// Mirror `readDashboardConn` from clip.mjs / minimax.mjs / usage.mjs.
|
|
20
|
+
function readDashboardConn() {
|
|
21
|
+
const cfgDir = process.env.XDG_CONFIG_HOME
|
|
22
|
+
? join(process.env.XDG_CONFIG_HOME, 'bizar')
|
|
23
|
+
: join(homedir(), '.config', 'bizar');
|
|
24
|
+
const portPath = join(cfgDir, 'dashboard.port');
|
|
25
|
+
const secretPath = join(cfgDir, 'dashboard.secret');
|
|
26
|
+
const port = existsSync(portPath)
|
|
27
|
+
? parseInt(readFileSync(portPath, 'utf8').trim(), 10)
|
|
28
|
+
: 4321;
|
|
29
|
+
const secret = existsSync(secretPath)
|
|
30
|
+
? readFileSync(secretPath, 'utf8').trim()
|
|
31
|
+
: '';
|
|
32
|
+
return {
|
|
33
|
+
port: Number.isFinite(port) && port > 0 ? port : 4321,
|
|
34
|
+
secret,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
19
37
|
|
|
20
38
|
const HOME = homedir();
|
|
21
39
|
const TEMPLATES_DIR = join(HOME, '.config', 'bizar', 'templates', 'eval-fixtures');
|
package/cli/commands/ocr.mjs
CHANGED
|
@@ -11,7 +11,24 @@
|
|
|
11
11
|
|
|
12
12
|
import chalk from 'chalk';
|
|
13
13
|
import { readFileSync, existsSync } from 'node:fs';
|
|
14
|
-
|
|
14
|
+
// Mirror `readDashboardConn` from clip.mjs / minimax.mjs / usage.mjs.
|
|
15
|
+
function readDashboardConn() {
|
|
16
|
+
const cfgDir = process.env.XDG_CONFIG_HOME
|
|
17
|
+
? require_('node:path').join(process.env.XDG_CONFIG_HOME, 'bizar')
|
|
18
|
+
: require_('node:path').join(require_('node:os').homedir(), '.config', 'bizar');
|
|
19
|
+
const portPath = require_('node:path').join(cfgDir, 'dashboard.port');
|
|
20
|
+
const secretPath = require_('node:path').join(cfgDir, 'dashboard.secret');
|
|
21
|
+
const port = require_('node:fs').existsSync(portPath)
|
|
22
|
+
? parseInt(require_('node:fs').readFileSync(portPath, 'utf8').trim(), 10)
|
|
23
|
+
: 4321;
|
|
24
|
+
const secret = require_('node:fs').existsSync(secretPath)
|
|
25
|
+
? require_('node:fs').readFileSync(secretPath, 'utf8').trim()
|
|
26
|
+
: '';
|
|
27
|
+
return {
|
|
28
|
+
port: Number.isFinite(port) && port > 0 ? port : 4321,
|
|
29
|
+
secret,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
15
32
|
|
|
16
33
|
// ── API helpers ────────────────────────────────────────────────────────────────
|
|
17
34
|
|
package/cli/commands/usage.mjs
CHANGED
|
@@ -105,5 +105,9 @@ async function runUsageCommand(args, wantJson = false) {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
export async function run(name, args, isHelpRequest) {
|
|
108
|
+
if (isHelpRequest || args.length === 0) {
|
|
109
|
+
showUsageHelp();
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
108
112
|
await runUsageCommand(args, false);
|
|
109
113
|
}
|
package/cli/commands/util.mjs
CHANGED
|
@@ -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';
|
|
@@ -154,6 +154,24 @@ export function showBackupHelp() {
|
|
|
154
154
|
`);
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
+
export function showProvidersHelp() {
|
|
158
|
+
console.log(`
|
|
159
|
+
bizar providers - Auto-detect provider API keys
|
|
160
|
+
|
|
161
|
+
Usage:
|
|
162
|
+
bizar providers detect Auto-detect provider API keys from env + cline.json
|
|
163
|
+
bizar providers --help Show this help
|
|
164
|
+
|
|
165
|
+
Description:
|
|
166
|
+
Scans process.env + ~/.config/cline/cline.json for known provider
|
|
167
|
+
API keys (OpenAI, Anthropic, OpenRouter, MiniMax, etc.) and reports
|
|
168
|
+
what's available. Useful before the first \`bizar install\` to verify
|
|
169
|
+
credentials are picked up.
|
|
170
|
+
|
|
171
|
+
No API keys are sent over the network - detection is local.
|
|
172
|
+
`);
|
|
173
|
+
}
|
|
174
|
+
|
|
157
175
|
export function showRestoreHelp() {
|
|
158
176
|
console.log(`
|
|
159
177
|
bizar restore — Restore BizarHarness from a backup
|
|
@@ -209,6 +227,18 @@ export async function runTestGate() {
|
|
|
209
227
|
|
|
210
228
|
export async function run(name, args, isHelpRequest) {
|
|
211
229
|
switch (name) {
|
|
230
|
+
case 'update':
|
|
231
|
+
// `bizar update` lives in commands/install.mjs (the install/update
|
|
232
|
+
// pair share a code path). Proxy to it.
|
|
233
|
+
if (isHelpRequest) {
|
|
234
|
+
const { showUpdateHelp } = await import('./install.mjs');
|
|
235
|
+
showUpdateHelp();
|
|
236
|
+
} else {
|
|
237
|
+
const { runUpdate } = await import('./install.mjs');
|
|
238
|
+
await runUpdate(args, {});
|
|
239
|
+
}
|
|
240
|
+
break;
|
|
241
|
+
|
|
212
242
|
case 'audit':
|
|
213
243
|
if (isHelpRequest) showAuditHelp();
|
|
214
244
|
else {
|
|
@@ -441,7 +471,75 @@ export async function run(name, args, isHelpRequest) {
|
|
|
441
471
|
break;
|
|
442
472
|
}
|
|
443
473
|
|
|
474
|
+
case 'agent-browser': {
|
|
475
|
+
// v6.0.0 — install / update / verify the agent-browser CLI.
|
|
476
|
+
// (The 'agent-browser-up' sibling is the bash wrapper that just
|
|
477
|
+
// manages the daemon process; this is the rich installer + updater.)
|
|
478
|
+
const { install, update, detectState, ensureRunning, printStatus } =
|
|
479
|
+
await import('../agent-browser-update.mjs');
|
|
480
|
+
const sub = args[0] || 'status';
|
|
481
|
+
switch (sub) {
|
|
482
|
+
case 'status':
|
|
483
|
+
printStatus();
|
|
484
|
+
break;
|
|
485
|
+
case 'install': {
|
|
486
|
+
const s = install({ silent: false });
|
|
487
|
+
console.log(chalk.green('\n agent-browser ready.'));
|
|
488
|
+
console.log(` version: ${s.version}`);
|
|
489
|
+
console.log(` daemon: ${s.daemonRunning ? 'running' : 'stopped'}`);
|
|
490
|
+
console.log(` profile: ${s.profileDir}`);
|
|
491
|
+
break;
|
|
492
|
+
}
|
|
493
|
+
case 'update': {
|
|
494
|
+
const s = update({ silent: false });
|
|
495
|
+
console.log(chalk.green('\n agent-browser up-to-date.'));
|
|
496
|
+
console.log(` version: ${s.version}`);
|
|
497
|
+
console.log(` daemon: ${s.daemonRunning ? 'running' : 'stopped'}`);
|
|
498
|
+
break;
|
|
499
|
+
}
|
|
500
|
+
case 'detect': {
|
|
501
|
+
const s = detectState();
|
|
502
|
+
console.log(JSON.stringify(s, null, 2));
|
|
503
|
+
break;
|
|
504
|
+
}
|
|
505
|
+
case 'start':
|
|
506
|
+
ensureRunning({ silent: false });
|
|
507
|
+
break;
|
|
508
|
+
case 'stop': {
|
|
509
|
+
const { spawnSync } = await import('node:child_process');
|
|
510
|
+
const killed = spawnSync('pkill', ['-f', 'agent-browser serve'], { stdio: 'ignore' });
|
|
511
|
+
if (killed.status === 0) {
|
|
512
|
+
console.log(chalk.green(' ✓ daemon stopped'));
|
|
513
|
+
} else {
|
|
514
|
+
console.log(chalk.dim(' daemon was not running'));
|
|
515
|
+
}
|
|
516
|
+
break;
|
|
517
|
+
}
|
|
518
|
+
default:
|
|
519
|
+
console.log(`bizar agent-browser <sub>
|
|
520
|
+
|
|
521
|
+
Subcommands:
|
|
522
|
+
status one-line status (default)
|
|
523
|
+
install install agent-browser + download Chrome
|
|
524
|
+
update upgrade to the latest version
|
|
525
|
+
detect JSON state (for scripts)
|
|
526
|
+
start start the daemon
|
|
527
|
+
stop stop the daemon
|
|
528
|
+
|
|
529
|
+
Examples:
|
|
530
|
+
bizar agent-browser status
|
|
531
|
+
bizar agent-browser install
|
|
532
|
+
bizar agent-browser update
|
|
533
|
+
bizar agent-browser start`);
|
|
534
|
+
}
|
|
535
|
+
break;
|
|
536
|
+
}
|
|
537
|
+
|
|
444
538
|
case 'providers':
|
|
539
|
+
if (isHelpRequest || args.length === 0) {
|
|
540
|
+
showProvidersHelp();
|
|
541
|
+
break;
|
|
542
|
+
}
|
|
445
543
|
if (args[0] === 'detect') {
|
|
446
544
|
const { runProvidersDetect } = await import('../providers-detect.mjs');
|
|
447
545
|
await runProvidersDetect(args.slice(1));
|
package/cli/digest.mjs
CHANGED
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
|
|
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
|
|
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.
|
|
3
|
+
"version": "5.6.0-beta.9",
|
|
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": {
|