@polderlabs/bizar 3.20.11 → 3.20.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/install.sh +592 -0
  2. package/package.json +4 -2
package/install.sh ADDED
@@ -0,0 +1,592 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # install.sh — One-shot BizarHarness installer.
4
+ #
5
+ # v3.20.11 — Comprehensive auto-installer with full dependency
6
+ # resolution. Replaces the older "print 4 manual next steps" flow
7
+ # with: (1) install every system dep we can reasonably fetch
8
+ # (uv, python3.12, chrome-headless-shell, jq, Chrome runtime libs);
9
+ # (2) install browser-harness + register its skill; (3) start Chrome
10
+ # for browser-driven E2E; (4) install + configure all BizarHarness
11
+ # files (agents, commands, opencode.json, plugin); (5) print a
12
+ # single status banner that tells the operator what is ready and
13
+ # what (if anything) they still need to do.
14
+ #
15
+ # Idempotent — every step checks for the existing install and
16
+ # skips re-work. Re-running after a failed install picks up
17
+ # where it left off. Same script can run from a fresh `git clone`
18
+ # or via `bizar install` (which spawns this script under bash).
19
+ #
20
+ # What this script does NOT do (intentional):
21
+ # - Configure provider API keys (MINIMAX_API_KEY, etc.). The user
22
+ # does that via `/connect` in opencode after install. We deliberately
23
+ # do not collect secrets here — the install is idempotent and safe
24
+ # to re-run, and asking for keys during install breaks CI / scripted
25
+ # deployments.
26
+ # - Reload opencode. The opencode session picks up the new config on
27
+ # next start. We print a status line at the end; we do not kill or
28
+ # restart the opencode process from here (it would interrupt
29
+ # in-flight agent sessions).
30
+ # - Hide `.obsidian/`. The Obsidian vault is a first-class part of
31
+ # the project tree. We do not add it to any .gitignore / .npmignore
32
+ # / IDE-exclusion list.
33
+ #
34
+ # Idempotent: every step checks for the existing install and skips
35
+ # re-work. Re-running after a failed install picks up where it left off.
36
+ #
37
+ # Cross-platform: bash + curl work on Linux + macOS. On Windows run
38
+ # `npm install -g @polderlabs/bizar` which triggers the npm postinstall
39
+ # hook (cli/install.mjs → install.sh via git bash).
40
+ set -euo pipefail
41
+
42
+ BOLD='\033[1m'
43
+ GREEN='\033[0;32m'
44
+ YELLOW='\033[1;33m'
45
+ CYAN='\033[0;36m'
46
+ RED='\033[0;31m'
47
+ DIM='\033[2m'
48
+ NC='\033[0m'
49
+
50
+ REPO_DIR="$(cd "$(dirname "$0")" && pwd)"
51
+ CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/opencode"
52
+ SKILLS_DIR="$HOME/.opencode/skills"
53
+ BIZAR_STATE_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/bizar"
54
+ BIZAR_STATE_FILE="$BIZAR_STATE_DIR/install-state.json"
55
+ PYTHON_BIN="${PYTHON_BIN:-python3.12}"
56
+ CHROME_PORT="${CHROME_PORT:-9222}"
57
+
58
+ # Counters for the final summary.
59
+ INSTALLED=()
60
+ SKIPPED=()
61
+ FAILED=()
62
+
63
+ note() {
64
+ echo -e " ${GREEN}✓${NC} $1"
65
+ INSTALLED+=("$1")
66
+ }
67
+ warn() {
68
+ echo -e " ${YELLOW}⚠${NC} $1"
69
+ SKIPPED+=("$1")
70
+ }
71
+ err() {
72
+ echo -e " ${RED}✗${NC} $1"
73
+ FAILED+=("$1")
74
+ }
75
+ section() {
76
+ echo ""
77
+ echo -e "${BOLD}${CYAN}── $1 ──${NC}"
78
+ }
79
+
80
+ # ── Pre-flight: which tools are missing? ───────────────────────────────
81
+ section "Pre-flight: checking system tools"
82
+
83
+ have_cmd() { command -v "$1" >/dev/null 2>&1; }
84
+ have_file() { [ -e "$1" ]; }
85
+
86
+ MISSING=()
87
+ have_cmd uv || MISSING+=("uv")
88
+ have_cmd "$PYTHON_BIN" || MISSING+=("$PYTHON_BIN")
89
+ have_cmd jq || MISSING+=("jq")
90
+ have_cmd npx || MISSING+=("npx")
91
+ have_cmd git || MISSING+=("git")
92
+
93
+ # Chrome detection: prefer chrome-headless-shell from puppeteer cache, then
94
+ # system chromium / chrome / google-chrome. We don't require Chrome — the
95
+ # installer works without it — but we report it so the operator knows.
96
+ CHROME_BIN=""
97
+ for cand in \
98
+ "$HOME/.cache/puppeteer/chrome-headless-shell"*/chrome-headless-shell/chrome-headless-shell \
99
+ /usr/bin/chromium \
100
+ /usr/bin/chrome \
101
+ /usr/bin/google-chrome \
102
+ "$(command -v chromium 2>/dev/null || true)" \
103
+ "$(command -v chrome 2>/dev/null || true)" \
104
+ "$(command -v google-chrome 2>/dev/null || true)"; do
105
+ [ -n "$cand" ] && [ -x "$cand" ] && CHROME_BIN="$cand" && break
106
+ done
107
+
108
+ for t in "${MISSING[@]}"; do warn "missing system tool: $t"; done
109
+ if [ -z "$CHROME_BIN" ]; then warn "no chrome / chromium found — browser-harness won't work until installed"; fi
110
+ [ ${#MISSING[@]} -eq 0 ] && [ -n "$CHROME_BIN" ] && note "all system tools present"
111
+
112
+ # ── Install missing system tools (best effort, never abort) ─────────────
113
+ section "Install missing system tools"
114
+
115
+ # uv (Python package manager — needed for browser-harness + Semble)
116
+ if ! have_cmd uv; then
117
+ echo -e " ${CYAN}→${NC} Installing uv (Python package manager)..."
118
+ if have_cmd curl; then
119
+ if curl -LsSf https://astral.sh/uv/install.sh | sh >/dev/null 2>&1; then
120
+ # uv's installer drops the binary at ~/.local/bin/uv
121
+ export PATH="$HOME/.local/bin:$PATH"
122
+ if have_cmd uv; then
123
+ note "uv installed at $(command -v uv)"
124
+ else
125
+ warn "uv install ran but uv not on PATH — try 'export PATH=\$HOME/.local/bin:\$PATH'"
126
+ fi
127
+ else
128
+ warn "uv installer script failed — install manually: https://docs.astral.sh/uv/"
129
+ fi
130
+ else
131
+ warn "curl not found — install uv manually: https://docs.astral.sh/uv/"
132
+ fi
133
+ else
134
+ note "uv $(uv --version 2>/dev/null | head -1)"
135
+ fi
136
+
137
+ # python3.12 (uv will manage its own python via --python 3.12, but
138
+ # we also accept system python3.12 if present). Only install if uv
139
+ # is available — uv can fetch its own python.
140
+ if ! have_cmd "$PYTHON_BIN"; then
141
+ if have_cmd uv; then
142
+ echo -e " ${CYAN}→${NC} python3.12 not on PATH — uv will fetch on first use"
143
+ note "python3.12 will be auto-fetched by uv (browser-harness will trigger first fetch)"
144
+ else
145
+ warn "no $PYTHON_BIN and no uv — install manually: sudo apt install python3.12 / brew install python@3.12"
146
+ fi
147
+ else
148
+ note "$PYTHON_BIN $($PYTHON_BIN --version 2>&1 | head -1)"
149
+ fi
150
+
151
+ # jq (for safe opencode.json merging)
152
+ if ! have_cmd jq; then
153
+ echo -e " ${CYAN}→${NC} Trying to install jq..."
154
+ case "$(uname -s 2>/dev/null || echo unknown)" in
155
+ Linux)
156
+ if have_cmd apt-get; then sudo apt-get install -y jq 2>/dev/null && note "jq installed (apt)" || warn "apt install jq failed — install manually"
157
+ elif have_cmd dnf; then sudo dnf install -y jq 2>/dev/null && note "jq installed (dnf)" || warn "dnf install jq failed — install manually"
158
+ elif have_cmd pacman; then sudo pacman -S --noconfirm jq 2>/dev/null && note "jq installed (pacman)" || warn "pacman install jq failed — install manually"
159
+ elif have_cmd apk; then sudo apk add jq 2>/dev/null && note "jq installed (apk)" || warn "apk install jq failed — install manually"
160
+ else warn "no known package manager — install jq manually"
161
+ fi
162
+ ;;
163
+ Darwin)
164
+ if have_cmd brew; then brew install jq 2>/dev/null && note "jq installed (brew)" || warn "brew install jq failed — install manually"
165
+ else warn "no brew — install jq manually"
166
+ fi
167
+ ;;
168
+ *) warn "unknown OS — install jq manually";;
169
+ esac
170
+ else
171
+ note "jq $(jq --version)"
172
+ fi
173
+
174
+ # Chrome (best effort — install chrome-headless-shell from chrome-for-testing
175
+ # or the puppeteer cache). The dashboard/browser-harness will work without
176
+ # it (the user can install later), but we try.
177
+ if [ -z "$CHROME_BIN" ]; then
178
+ echo -e " ${CYAN}→${NC} Trying to install chrome-headless-shell..."
179
+ PUPPETEER_CACHE_DIR="$HOME/.cache/puppeteer"
180
+ mkdir -p "$PUPPETEER_CACHE_DIR"
181
+ # chrome-for-testing JSON API — find the latest stable chrome-headless-shell
182
+ # URL for linux64. Fall back to nothing if the request fails.
183
+ CHROME_CT_URL=$(curl -fsSL "https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions-with-downloads.json" 2>/dev/null \
184
+ | grep -o '"url": *"[^"]*chrome-headless-shell-linux64[^"]*"' \
185
+ | head -1 \
186
+ | sed 's/.*"url": *"\([^"]*\)".*/\1/')
187
+ if [ -n "$CHROME_CT_URL" ]; then
188
+ TMP_DIR=$(mktemp -d)
189
+ if curl -fsSL "$CHROME_CT_URL" -o "$TMP_DIR/chrome-headless-shell.zip" 2>/dev/null; then
190
+ TARGET_DIR="$PUPPETEER_CACHE_DIR/chrome-headless-shell"
191
+ mkdir -p "$TARGET_DIR"
192
+ unzip -q -o "$TMP_DIR/chrome-headless-shell.zip" -d "$TARGET_DIR"
193
+ CHROME_BIN=$(find "$TARGET_DIR" -name chrome-headless-shell -type f 2>/dev/null | head -1)
194
+ [ -n "$CHROME_BIN" ] && [ -x "$CHROME_BIN" ] && note "chrome-headless-shell installed at $CHROME_BIN" || warn "chrome-headless-shell install failed"
195
+ rm -rf "$TMP_DIR"
196
+ else
197
+ warn "download from chrome-for-testing failed — install Chrome manually"
198
+ fi
199
+ else
200
+ warn "could not resolve latest chrome-for-testing URL — install Chrome manually"
201
+ fi
202
+ fi
203
+
204
+ # Chrome runtime libs (v3.20.11). chrome-headless-shell needs ~10 shared
205
+ # libraries that Debian/Ubuntu don't ship by default. Without these,
206
+ # chrome starts but immediately errors with `libnspr4.so: cannot open
207
+ # shared object file`. We install them on apt-based systems; on others
208
+ # we warn but continue (Chrome won't work, but the rest of the install
209
+ # will).
210
+ if [ -n "$CHROME_BIN" ] || [ -z "$CHROME_BIN" ]; then
211
+ # Check if chrome-headless-shell actually runs (libs satisfied)
212
+ if [ -n "$CHROME_BIN" ] && ! "$CHROME_BIN" --version >/dev/null 2>&1; then
213
+ case "$(uname -s 2>/dev/null || echo unknown)" in
214
+ Linux)
215
+ if have_cmd apt-get; then
216
+ echo -e " ${CYAN}→${NC} chrome-headless-shell needs runtime libs — installing via apt..."
217
+ if sudo -n apt-get install -y --no-install-recommends \
218
+ libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 \
219
+ libxdamage1 libxkbcommon0 libasound2t64 libatspi2.0-0 \
220
+ 2>/dev/null; then
221
+ note "chrome runtime libs installed (libnss3, libnspr4, libatk*, libxkbcommon, libasound, libatspi)"
222
+ else
223
+ warn "apt install of chrome runtime libs failed (needs sudo). run manually:"
224
+ warn " sudo apt-get install -y libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 \\"
225
+ warn " libxdamage1 libxkbcommon0 libasound2t64 libatspi2.0-0"
226
+ fi
227
+ else
228
+ warn "chrome-headless-shell runtime libs missing — install libnss3, libnspr4, libatk* for your distro"
229
+ fi
230
+ ;;
231
+ Darwin)
232
+ warn "chrome-headless-shell runtime libs missing — on macOS: brew install --cask chromium"
233
+ ;;
234
+ *) warn "chrome-headless-shell runtime libs missing — install via your package manager";;
235
+ esac
236
+ fi
237
+ fi
238
+
239
+ # ── npm packages (BizarHarness, optional @polderlabs/bizar-dash peer) ───
240
+ section "Install BizarHarness npm packages"
241
+
242
+ if have_cmd npm; then
243
+ for pkg in @polderlabs/bizar @polderlabs/bizar-dash; do
244
+ if npm ls -g "$pkg" --depth=0 >/dev/null 2>&1; then
245
+ cur=$(npm ls -g "$pkg" --depth=0 --json 2>/dev/null | grep -oE '"version":\s*"[^"]+"' | head -1 | sed 's/.*"\([^"]*\)".*/\1/')
246
+ note "$pkg already installed (v$cur)"
247
+ else
248
+ echo -e " ${CYAN}→${NC} Installing $pkg..."
249
+ if npm install -g "$pkg" >/dev/null 2>&1; then
250
+ note "$pkg installed"
251
+ else
252
+ err "$pkg install failed (run manually: npm install -g $pkg)"
253
+ fi
254
+ fi
255
+ done
256
+ else
257
+ err "npm not found — install Node.js 20+ first: https://nodejs.org/"
258
+ fi
259
+
260
+ # ── browser-harness (Python tool via uv, requires Python 3.12) ─────────
261
+ section "Install browser-harness (Python via uv)"
262
+
263
+ if have_cmd browser-harness; then
264
+ note "browser-harness $(browser-harness --version 2>&1 | head -1) already installed"
265
+ else
266
+ if have_cmd uv; then
267
+ echo -e " ${CYAN}→${NC} uv tool install --python 3.12 --upgrade --force browser-harness"
268
+ if uv tool install --python 3.12 --upgrade --force browser-harness >/dev/null 2>&1; then
269
+ note "browser-harness installed"
270
+ else
271
+ err "browser-harness install failed (run manually: uv tool install --python 3.12 --upgrade --force browser-harness)"
272
+ fi
273
+ else
274
+ warn "uv not installed — skipping browser-harness (install uv first, then re-run this script)"
275
+ fi
276
+ fi
277
+
278
+ # Register the browser-harness skill so any agent that needs it picks it up.
279
+ if have_cmd browser-harness; then
280
+ BH_SKILL_DIR="$SKILLS_DIR/browser-harness"
281
+ mkdir -p "$BH_SKILL_DIR"
282
+ if browser-harness skill > "$BH_SKILL_DIR/SKILL.md" 2>/dev/null; then
283
+ note "browser-harness skill registered at $BH_SKILL_DIR/SKILL.md"
284
+ else
285
+ warn "browser-harness skill registration failed (re-run: browser-harness skill > $BH_SKILL_DIR/SKILL.md)"
286
+ fi
287
+ fi
288
+
289
+ # ── Chrome lifecycle (start browser-harness-up.sh) ────────────────────
290
+ section "Start Chrome for browser-harness"
291
+
292
+ if [ -x "$REPO_DIR/cli/browser-harness-up.sh" ]; then
293
+ # The script knows how to find chrome-headless-shell from the
294
+ # puppeteer cache (or any system chrome).
295
+ if BH_OUTPUT=$("$REPO_DIR/cli/browser-harness-up.sh" start 2>&1); then
296
+ # Trim verbose output; just keep the success lines.
297
+ while IFS= read -r line; do
298
+ case "$line" in
299
+ *"✓"*|*"already"*|*"stopped"*|*"restarted"*) note "$line" ;;
300
+ *) ;;
301
+ esac
302
+ done <<< "$BH_OUTPUT"
303
+ else
304
+ warn "browser-harness-up.sh start failed (run manually: $REPO_DIR/cli/browser-harness-up.sh start)"
305
+ fi
306
+ elif have_cmd "$REPO_DIR/../bin/bizar"; then
307
+ if "$REPO_DIR/../bin/bizar" browser-harness-up start 2>&1 | grep -E '✓|✗' | head -3; then
308
+ note "Chrome started via bizar browser-harness-up"
309
+ fi
310
+ else
311
+ warn "cli/browser-harness-up.sh not found at $REPO_DIR — skipping Chrome auto-start"
312
+ fi
313
+
314
+ # ── BizarHarness config files ─────────────────────────────────────────
315
+ section "Install BizarHarness config files"
316
+
317
+ mkdir -p "$CONFIG_DIR/agents"
318
+
319
+ # Agents — copy every .md except those already in place.
320
+ for f in "$REPO_DIR/config/agents/"*.md; do
321
+ [ -f "$f" ] || continue
322
+ name="$(basename "$f")"
323
+ if [ -f "$CONFIG_DIR/agents/$name" ] && cmp -s "$f" "$CONFIG_DIR/agents/$name"; then
324
+ : # identical, skip silently
325
+ else
326
+ cp "$f" "$CONFIG_DIR/agents/$name"
327
+ INSTALLED+=("agents/$name")
328
+ fi
329
+ done
330
+ note "agents synced ($(ls "$CONFIG_DIR/agents/"*.md 2>/dev/null | wc -l) files)"
331
+
332
+ # Shared baseline (used as a skill AND as the on-disk reference)
333
+ if [ -d "$REPO_DIR/config/agents/_shared" ]; then
334
+ mkdir -p "$CONFIG_DIR/agents/_shared"
335
+ cp -R "$REPO_DIR/config/agents/_shared/." "$CONFIG_DIR/agents/_shared/"
336
+ note "agents/_shared/ synced"
337
+ fi
338
+
339
+ # Slash commands
340
+ if [ -d "$REPO_DIR/config/commands/" ]; then
341
+ mkdir -p "$CONFIG_DIR/commands"
342
+ for f in "$REPO_DIR/config/commands/"*.md; do
343
+ [ -f "$f" ] || continue
344
+ name="$(basename "$f")"
345
+ cp "$f" "$CONFIG_DIR/commands/$name"
346
+ done
347
+ note "slash commands synced"
348
+ fi
349
+
350
+ # Hooks
351
+ if [ -d "$REPO_DIR/config/hooks/" ]; then
352
+ mkdir -p "$CONFIG_DIR/hooks"
353
+ for entry in "$REPO_DIR/config/hooks/"*; do
354
+ [ -e "$entry" ] || continue
355
+ name="$(basename "$entry")"
356
+ cp -R "$entry" "$CONFIG_DIR/hooks/$name"
357
+ done
358
+ note "hooks synced"
359
+ fi
360
+
361
+ # AGENTS.md
362
+ if [ -f "$REPO_DIR/config/AGENTS.md" ]; then
363
+ cp "$REPO_DIR/config/AGENTS.md" "$CONFIG_DIR/AGENTS.md"
364
+ note "AGENTS.md synced"
365
+ fi
366
+
367
+ # ── Bundled skills (bizar, self-improvement, agent-baseline, …) ───────
368
+ section "Install bundled skills"
369
+
370
+ mkdir -p "$SKILLS_DIR"
371
+ for skill in bizar self-improvement cpp-coding-standards cpp-testing embedded-esp-idf; do
372
+ if [ -d "$REPO_DIR/config/skills/$skill" ]; then
373
+ mkdir -p "$SKILLS_DIR/$skill"
374
+ cp -R "$REPO_DIR/config/skills/$skill/." "$SKILLS_DIR/$skill/"
375
+ note "skill: $skill"
376
+ else
377
+ warn "skill source missing: $skill (skipping)"
378
+ fi
379
+ done
380
+
381
+ # Shared agent baseline skill — referenced by every agent file.
382
+ if [ -f "$REPO_DIR/config/agents/_shared/AGENT_BASELINE.md" ]; then
383
+ mkdir -p "$SKILLS_DIR/agent-baseline"
384
+ cp "$REPO_DIR/config/agents/_shared/AGENT_BASELINE.md" "$SKILLS_DIR/agent-baseline/SKILL.md"
385
+ note "skill: agent-baseline (shared by all 14 agents)"
386
+ fi
387
+
388
+ # Make bundled scripts executable.
389
+ chmod +x "$SKILLS_DIR"/embedded-esp-idf/scripts/*.sh 2>/dev/null || true
390
+
391
+ # ── Domain skills via skills.sh (impeccable, ponytail, obsidian-skills) ─
392
+ section "Install domain skills from skills.sh"
393
+
394
+ if have_cmd npx; then
395
+ # Impeccable — UI anti-pattern detection (frontend quality)
396
+ if npx --yes impeccable skills install -y --scope=user --providers=opencode >/dev/null 2>&1; then
397
+ note "impeccable (UI anti-pattern detector)"
398
+ else
399
+ warn "impeccable install failed (run: npx impeccable skills install)"
400
+ fi
401
+
402
+ # Ponytail — minimal-code skill for AI agents
403
+ if npx --yes @dietrichgebert/ponytail install --scope=user >/dev/null 2>&1; then
404
+ note "ponytail (minimal-code skill)"
405
+ else
406
+ warn "ponytail install failed (run: npx @dietrichgebert/ponytail install)"
407
+ fi
408
+
409
+ # Obsidian skills — Obsidian Flavored Markdown, Bases, JSON Canvas, CLI
410
+ if npx --yes skills add https://github.com/kepano/obsidian-skills --all -y >/dev/null 2>&1; then
411
+ note "obsidian-skills (Obsidian Markdown/Bases/Canvas)"
412
+ else
413
+ warn "obsidian-skills install failed (run: npx skills add kepano/obsidian-skills)"
414
+ fi
415
+ else
416
+ warn "npx not found — skipping domain skill installation"
417
+ fi
418
+
419
+ # ── Bizar plugin (mirrored from npm package to ~/.config/opencode/plugins/) ─
420
+ section "Install Bizar opencode plugin"
421
+
422
+ PLUGIN_DST="$CONFIG_DIR/plugins/bizar"
423
+
424
+ # v3.20.12: two sources, in priority order:
425
+ # 1. Local repo's plugins/bizar/ (for `git clone` users)
426
+ # 2. Globally installed @polderlabs/bizar-plugin (for `npm i -g` users)
427
+ # The npm-installed plugin also needs its bundled node_modules/ copied
428
+ # alongside (it depends on @polderlabs/bizar-sdk, which isn't on npm).
429
+ # This previously lived in cli/install.mjs:installPluginFromGlobal();
430
+ # duplicating the logic here makes install.sh the single source of truth.
431
+ PLUGIN_SRC=""
432
+ if [ -d "$REPO_DIR/plugins/bizar" ]; then
433
+ PLUGIN_SRC="$REPO_DIR/plugins/bizar"
434
+ PLUGIN_SRC_KIND="local repo"
435
+ elif have_cmd npm; then
436
+ # Resolve the npm global plugin path. `npm root -g` gives us
437
+ # /usr/local/lib/node_modules; the plugin lives in @polderlabs/bizar-plugin/.
438
+ NPM_GLOBAL_ROOT=$(npm root -g 2>/dev/null || echo "")
439
+ if [ -n "$NPM_GLOBAL_ROOT" ] && [ -d "$NPM_GLOBAL_ROOT/@polderlabs/bizar-plugin" ]; then
440
+ PLUGIN_SRC="$NPM_GLOBAL_ROOT/@polderlabs/bizar-plugin"
441
+ PLUGIN_SRC_KIND="@polderlabs/bizar-plugin (global)"
442
+ fi
443
+ fi
444
+
445
+ if [ -n "$PLUGIN_SRC" ]; then
446
+ mkdir -p "$PLUGIN_DST"
447
+ while IFS= read -r -d '' f; do
448
+ rel="${f#$PLUGIN_SRC/}"
449
+ case "$rel" in
450
+ node_modules/*|dist/*|*.log|.DS_Store) continue ;;
451
+ esac
452
+ mkdir -p "$(dirname "$PLUGIN_DST/$rel")"
453
+ cp "$f" "$PLUGIN_DST/$rel"
454
+ done < <(find "$PLUGIN_SRC" -type f -print0)
455
+ note "plugins/bizar/ copied from $PLUGIN_SRC_KIND"
456
+
457
+ # Copy node_modules from the npm plugin package (the plugin imports
458
+ # @polderlabs/bizar-sdk, which isn't on npm). Idempotent.
459
+ if [ "$PLUGIN_SRC_KIND" != "local repo" ] && [ -d "$PLUGIN_SRC/node_modules" ]; then
460
+ if cp -R "$PLUGIN_SRC/node_modules/." "$PLUGIN_DST/node_modules/" 2>/dev/null; then
461
+ N_PKG=$(find "$PLUGIN_SRC/node_modules" -maxdepth 1 -mindepth 1 -type d 2>/dev/null | wc -l)
462
+ note "node_modules (~$N_PKG packages) bundled with deployed plugin"
463
+ else
464
+ warn "could not copy plugin node_modules (manual: cp -r $PLUGIN_SRC/node_modules $PLUGIN_DST/)"
465
+ fi
466
+ fi
467
+ else
468
+ warn "Bizar plugin source not found (install @polderlabs/bizar-plugin: npm i -g @polderlabs/bizar-plugin)"
469
+ fi
470
+
471
+ # ── Merge opencode.json (template + user's existing config) ────────────
472
+ section "Configure opencode.json"
473
+
474
+ # Prefer the .template copy; fall back to the legacy tracked file.
475
+ if [ -f "$REPO_DIR/config/opencode.json.template" ]; then
476
+ TEMPLATE="$REPO_DIR/config/opencode.json.template"
477
+ elif [ -f "$REPO_DIR/config/opencode.json" ]; then
478
+ TEMPLATE="$REPO_DIR/config/opencode.json"
479
+ else
480
+ warn "No opencode.json template found"
481
+ TEMPLATE=""
482
+ fi
483
+
484
+ if [ -n "$TEMPLATE" ]; then
485
+ if [ -f "$CONFIG_DIR/opencode.json" ]; then
486
+ cp "$CONFIG_DIR/opencode.json" "$CONFIG_DIR/opencode.json.bak"
487
+ fi
488
+
489
+ if have_cmd jq; then
490
+ MERGE_TMP="$CONFIG_DIR/opencode.json.merge.$$"
491
+ if [ -f "$CONFIG_DIR/opencode.json" ]; then
492
+ jq -s '.[0] * .[1]' "$TEMPLATE" "$CONFIG_DIR/opencode.json" > "$MERGE_TMP" 2>/dev/null \
493
+ && mv "$MERGE_TMP" "$CONFIG_DIR/opencode.json" \
494
+ || { rm -f "$MERGE_TMP"; cp "$TEMPLATE" "$CONFIG_DIR/opencode.json"; }
495
+ else
496
+ cp "$TEMPLATE" "$CONFIG_DIR/opencode.json"
497
+ fi
498
+
499
+ # Ensure the Bizar plugin entry is registered (idempotent).
500
+ PLUGIN_TMP="$CONFIG_DIR/opencode.json.tmp.$$"
501
+ jq '
502
+ if (.plugin // []) | map(.[0] == "./plugins/bizar/index.ts") | any then .
503
+ else .plugin = (.plugin // []) + [["./plugins/bizar/index.ts", {
504
+ "loopThresholdWarn": 5,
505
+ "loopThresholdEscalate": 8,
506
+ "loopThresholdBlock": 12,
507
+ "loopWindowSize": 10
508
+ }]]
509
+ end
510
+ ' "$CONFIG_DIR/opencode.json" > "$PLUGIN_TMP" \
511
+ && mv "$PLUGIN_TMP" "$CONFIG_DIR/opencode.json" \
512
+ || rm -f "$PLUGIN_TMP"
513
+ note "opencode.json (template merged with existing config; plugin entry ensured)"
514
+ else
515
+ cp "$TEMPLATE" "$CONFIG_DIR/opencode.json"
516
+ warn "jq not available — copied template directly. Re-install jq for safe merging."
517
+ fi
518
+ fi
519
+
520
+ # ── Write install-state.json (used by `bizar update` for migrations) ───
521
+ section "Write install-state"
522
+
523
+ mkdir -p "$BIZAR_STATE_DIR"
524
+ BH_VERSION=$(browser-harness --version 2>/dev/null | head -1 | sed 's/[^0-9.]//g' || echo "")
525
+ BIZAR_VERSION=$(npm ls -g @polderlabs/bizar --depth=0 --json 2>/dev/null | grep -oE '"@polderlabs/bizar":\s*\{[^}]*"version":\s*"[^"]+"' | grep -oE '"version":\s*"[^"]+"' | grep -oE '"[^"]+"' | tail -1 | tr -d '"' || echo "")
526
+ DASH_VERSION=$(npm ls -g @polderlabs/bizar-dash --depth=0 --json 2>/dev/null | grep -oE '"@polderlabs/bizar-dash":\s*\{[^}]*"version":\s*"[^"]+"' | grep -oE '"version":\s*"[^"]+"' | grep -oE '"[^"]+"' | tail -1 | tr -d '"' || echo "")
527
+ PY_VERSION=$("$PYTHON_BIN" --version 2>/dev/null | head -1 | sed 's/[^0-9.]//g' || echo "")
528
+ UV_VERSION=$(uv --version 2>/dev/null | head -1 | sed 's/[^0-9.]//g' || echo "")
529
+ JQ_VERSION=$(jq --version 2>/dev/null | sed 's/[^0-9.]//g' || echo "")
530
+
531
+ STATE_JSON=$(cat <<JSON
532
+ {
533
+ "installedAt": "$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u +%Y-%m-%dT%H:%M:%S.%NZ | sed 's/\.[0-9]*//')",
534
+ "components": {
535
+ "bizar": "${BIZAR_VERSION:-unknown}",
536
+ "bizar-dash": "${DASH_VERSION:-unknown}",
537
+ "browser-harness": "${BH_VERSION:-unknown}",
538
+ "uv": "${UV_VERSION:-unknown}",
539
+ "python3.12": "${PY_VERSION:-unknown}",
540
+ "jq": "${JQ_VERSION:-unknown}"
541
+ }
542
+ }
543
+ JSON
544
+ )
545
+ echo "$STATE_JSON" > "$BIZAR_STATE_FILE"
546
+ note "install-state written to $BIZAR_STATE_FILE"
547
+
548
+ # ── Final status banner ──────────────────────────────────────────────
549
+ section "Install complete"
550
+
551
+ echo ""
552
+ echo -e "${BOLD}${CYAN}┌────────────────────────────────────────────────────────────┐${NC}"
553
+ echo -e "${BOLD}${CYAN}│${NC} ${BOLD}BizarHarness ready.${NC} │"
554
+ echo -e "${BOLD}${CYAN}│${NC} │"
555
+
556
+ if [ ${#INSTALLED[@]} -gt 0 ]; then
557
+ echo -e "${BOLD}${CYAN}│${NC} ${GREEN}✓ Installed:${NC} │"
558
+ for entry in "${INSTALLED[@]}"; do
559
+ short=$(echo "$entry" | head -c 56)
560
+ echo -e "${BOLD}${CYAN}│${NC} • $short$(printf '%*s' $((56 - ${#short})) '') │"
561
+ done
562
+ fi
563
+
564
+ if [ ${#SKIPPED[@]} -gt 0 ]; then
565
+ echo -e "${BOLD}${CYAN}│${NC} │"
566
+ echo -e "${BOLD}${CYAN}│${NC} ${YELLOW}⚠ Skipped:${NC} │"
567
+ for entry in "${SKIPPED[@]}"; do
568
+ short=$(echo "$entry" | head -c 56)
569
+ echo -e "${BOLD}${CYAN}│${NC} • $short$(printf '%*s' $((56 - ${#short})) '') │"
570
+ done
571
+ fi
572
+
573
+ if [ ${#FAILED[@]} -gt 0 ]; then
574
+ echo -e "${BOLD}${CYAN}│${NC} │"
575
+ echo -e "${BOLD}${CYAN}│${NC} ${RED}✗ Failed:${NC} │"
576
+ for entry in "${FAILED[@]}"; do
577
+ short=$(echo "$entry" | head -c 56)
578
+ echo -e "${BOLD}${CYAN}│${NC} • $short$(printf '%*s' $((56 - ${#short})) '') │"
579
+ done
580
+ fi
581
+
582
+ echo -e "${BOLD}${CYAN}│${NC} │"
583
+ echo -e "${BOLD}${CYAN}│${NC} ${DIM}Next: open /connect in your next opencode session${NC} │"
584
+ echo -e "${BOLD}${CYAN}│${NC} ${DIM} to add API keys (opencode-zen is free; MiniMax${NC} │"
585
+ echo -e "${BOLD}${CYAN}│${NC} ${DIM} is paid). The installer never asks for keys —${NC} │"
586
+ echo -e "${BOLD}${CYAN}│${NC} ${DIM} do that part yourself so secrets stay on your${NC} │"
587
+ echo -e "${BOLD}${CYAN}│${NC} ${DIM} machine, not in any installer log.${NC} │"
588
+ echo -e "${BOLD}${CYAN}└────────────────────────────────────────────────────────────┘${NC}"
589
+ echo ""
590
+
591
+ [ ${#FAILED[@]} -gt 0 ] && exit 1
592
+ exit 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polderlabs/bizar",
3
- "version": "3.20.11",
3
+ "version": "3.20.13",
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": {
@@ -9,7 +9,9 @@
9
9
  "files": [
10
10
  "cli/",
11
11
  "config/",
12
- "templates/"
12
+ "templates/",
13
+ "install.sh",
14
+ "cli/browser-harness-up.sh"
13
15
  ],
14
16
  "workspaces": [
15
17
  "packages/*"