@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.
- package/install.sh +592 -0
- 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.
|
|
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/*"
|