@polderlabs/bizar 4.3.0 → 4.4.0
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/bizar-dash/src/server/opencode-sdk.mjs +72 -0
- package/bizar-dash/src/server/routes/background.mjs +92 -0
- package/bizar-dash/src/server/routes/chat.mjs +300 -123
- package/bizar-dash/src/server/routes/opencode-session-detail.mjs +26 -0
- package/bizar-dash/src/server/routes/tasks.mjs +59 -1
- package/bizar-dash/src/server/task-delegator.mjs +154 -8
- package/bizar-dash/src/server/tasks-store.mjs +50 -2
- package/bizar-dash/src/web/components/background/AttachButton.tsx +96 -0
- package/bizar-dash/src/web/components/background/TmuxAttachCard.tsx +122 -0
- package/bizar-dash/src/web/components/chat/AgentNode.tsx +127 -0
- package/bizar-dash/src/web/components/chat/AgentTree.tsx +80 -0
- package/bizar-dash/src/web/components/chat/ChatComposer.tsx +76 -0
- package/bizar-dash/src/web/components/chat/ChatInfoPanel.tsx +144 -0
- package/bizar-dash/src/web/components/chat/ChatRail.tsx +387 -0
- package/bizar-dash/src/web/components/chat/ChatThread.tsx +28 -7
- package/bizar-dash/src/web/components/chat/JumpToLatest.tsx +58 -0
- package/bizar-dash/src/web/components/chat/MessageBlock.tsx +260 -0
- package/bizar-dash/src/web/components/chat/SessionRowMenu.tsx +206 -0
- package/bizar-dash/src/web/components/chat/StreamingIndicator.tsx +33 -5
- package/bizar-dash/src/web/components/chat/_legacy.ts +30 -0
- package/bizar-dash/src/web/components/chat/index.ts +11 -0
- package/bizar-dash/src/web/components/chat/useChat.ts +345 -167
- package/bizar-dash/src/web/components/tasks/BacklogPanel.css +109 -0
- package/bizar-dash/src/web/components/tasks/BacklogPanel.tsx +209 -0
- package/bizar-dash/src/web/styles/chat.css +1536 -133
- package/bizar-dash/src/web/views/BackgroundAgents.tsx +3 -0
- package/bizar-dash/src/web/views/Chat.tsx +147 -57
- package/bizar-dash/src/web/views/Tasks.tsx +23 -1
- package/cli/bg.mjs +94 -71
- package/cli/bin.mjs +19 -7
- package/cli/service-controller.mjs +587 -0
- package/cli/service-controller.test.mjs +92 -0
- package/cli/service.mjs +162 -14
- package/config/agents/baldr.md +2 -0
- package/config/agents/browser-harness.md +2 -0
- package/config/agents/forseti.md +2 -0
- package/config/agents/frigg.md +2 -0
- package/config/agents/heimdall.md +2 -0
- package/config/agents/hermod.md +2 -0
- package/config/agents/mimir.md +2 -0
- package/config/agents/odin.md +2 -0
- package/config/agents/quick.md +2 -0
- package/config/agents/semble-search.md +2 -0
- package/config/agents/thor.md +2 -0
- package/config/agents/tyr.md +2 -0
- package/config/agents/vidarr.md +2 -0
- package/config/agents/vor.md +2 -0
- package/config/opencode.json.template +1 -0
- package/install.sh +448 -787
- package/package.json +1 -1
- package/packages/sdk/package.json +20 -0
- package/packages/sdk/src/client.ts +5 -0
- package/packages/sdk/src/errors.ts +11 -2
- package/packages/sdk/src/index.ts +19 -0
- package/packages/sdk/src/opencode-events.ts +134 -0
- package/packages/sdk/src/opencode-types.ts +66 -0
- package/packages/sdk/src/opencode.ts +335 -0
package/install.sh
CHANGED
|
@@ -1,47 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
#
|
|
3
|
-
# install.sh —
|
|
3
|
+
# install.sh — Cross-platform BizarHarness installer.
|
|
4
4
|
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
5
|
+
# v3.22.0 — Rewritten for cross-platform dispatch. Linux (apt/dnf/pacman/zypper),
|
|
6
|
+
# macOS (brew), and a pointer to install.ps1 for Windows.
|
|
7
7
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
# Idempotent — every step checks for the existing install and
|
|
19
|
-
# skips re-work. Re-running after a failed install picks up
|
|
20
|
-
# where it left off. Same script can run from a fresh `git clone`
|
|
21
|
-
# or via `bizar install` (which spawns this script under bash).
|
|
8
|
+
# Features:
|
|
9
|
+
# - Dependency detection via node scripts/check-deps.mjs
|
|
10
|
+
# - Auto-install missing deps for the current platform/distro
|
|
11
|
+
# - Idempotent — every step checks before acting
|
|
12
|
+
# - --update: git pull --ff-only, re-install config, re-install service
|
|
13
|
+
# - --dry-run: print what would happen, do nothing
|
|
14
|
+
# - --non-interactive: skip all prompts (CI safe)
|
|
15
|
+
# - --force: overwrite files even when they match
|
|
16
|
+
# - Plugin registration verification in ~/.config/opencode/opencode.json
|
|
17
|
+
# - Service registration via node scripts/install-service.mjs
|
|
22
18
|
#
|
|
23
19
|
# What this script does NOT do (intentional):
|
|
24
|
-
# - Configure provider API keys
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
|
|
28
|
-
# deployments.
|
|
29
|
-
# - Reload opencode. The opencode session picks up the new config on
|
|
30
|
-
# next start. We print a status line at the end; we do not kill or
|
|
31
|
-
# restart the opencode process from here (it would interrupt
|
|
32
|
-
# in-flight agent sessions).
|
|
33
|
-
# - Hide `.obsidian/`. The Obsidian vault is a first-class part of
|
|
34
|
-
# the project tree. We do not add it to any .gitignore / .npmignore
|
|
35
|
-
# / IDE-exclusion list.
|
|
36
|
-
#
|
|
37
|
-
# Idempotent: every step checks for the existing install and skips
|
|
38
|
-
# re-work. Re-running after a failed install picks up where it left off.
|
|
39
|
-
#
|
|
40
|
-
# Cross-platform: bash + curl work on Linux + macOS. On Windows run
|
|
41
|
-
# `npm install -g @polderlabs/bizar` which triggers the npm postinstall
|
|
42
|
-
# hook (cli/install.mjs → install.sh via git bash).
|
|
20
|
+
# - Configure provider API keys. The user does that via /connect.
|
|
21
|
+
# - Reload opencode. The next opencode session picks up new config.
|
|
22
|
+
# - Windows installation (see install.ps1 for that).
|
|
23
|
+
|
|
43
24
|
set -euo pipefail
|
|
44
25
|
|
|
26
|
+
# ── Constants ──────────────────────────────────────────────────────────────────
|
|
27
|
+
|
|
45
28
|
BOLD='\033[1m'
|
|
46
29
|
GREEN='\033[0;32m'
|
|
47
30
|
YELLOW='\033[1;33m'
|
|
@@ -52,824 +35,502 @@ NC='\033[0m'
|
|
|
52
35
|
|
|
53
36
|
REPO_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
54
37
|
CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/opencode"
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
echo -e " ${YELLOW}⚠${NC} $1"
|
|
72
|
-
SKIPPED+=("$1")
|
|
73
|
-
}
|
|
74
|
-
err() {
|
|
75
|
-
echo -e " ${RED}✗${NC} $1"
|
|
76
|
-
FAILED+=("$1")
|
|
77
|
-
}
|
|
78
|
-
section() {
|
|
38
|
+
PLUGIN_DIR="$CONFIG_DIR/plugins/bizar"
|
|
39
|
+
|
|
40
|
+
# Flags parsed from CLI args
|
|
41
|
+
DRY_RUN=false
|
|
42
|
+
NON_INTERACTIVE=false
|
|
43
|
+
FORCE=false
|
|
44
|
+
UPDATE_MODE=false
|
|
45
|
+
|
|
46
|
+
# ── UI helpers ─────────────────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
note() { echo -e " ${GREEN}✓${NC} $1"; }
|
|
49
|
+
warn() { echo -e " ${YELLOW}⚠${NC} $1"; }
|
|
50
|
+
err() { echo -e " ${RED}✗${NC} $1"; }
|
|
51
|
+
action(){ echo -e " ${CYAN}→${NC} $1"; }
|
|
52
|
+
dim() { echo -e " ${DIM}$1${NC}"; }
|
|
53
|
+
section(){
|
|
79
54
|
echo ""
|
|
80
55
|
echo -e "${BOLD}${CYAN}── $1 ──${NC}"
|
|
81
56
|
}
|
|
82
57
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
58
|
+
have_cmd() { command -v "$1" >/dev/null 2>&1; }
|
|
59
|
+
|
|
60
|
+
# ── Usage ──────────────────────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
show_usage() {
|
|
63
|
+
cat <<'EOF'
|
|
64
|
+
|
|
65
|
+
install.sh — BizarHarness cross-platform installer
|
|
66
|
+
|
|
67
|
+
Usage:
|
|
68
|
+
./install.sh Interactive install (Linux/macOS)
|
|
69
|
+
./install.sh --help Show this help
|
|
70
|
+
./install.sh --dry-run Dry run — print actions, no changes
|
|
71
|
+
./install.sh --non-interactive Non-interactive (CI safe)
|
|
72
|
+
./install.sh --force Overwrite existing files
|
|
73
|
+
./install.sh --update Pull latest + re-install config + service
|
|
74
|
+
|
|
75
|
+
On Windows, run install.ps1 instead.
|
|
76
|
+
|
|
77
|
+
EOF
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# ── Parse CLI flags ────────────────────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
parse_flags() {
|
|
83
|
+
for arg in "$@"; do
|
|
84
|
+
case "$arg" in
|
|
85
|
+
--help|-h) show_usage; exit 0 ;;
|
|
86
|
+
--dry-run) DRY_RUN=true ;;
|
|
87
|
+
--non-interactive) NON_INTERACTIVE=true ;;
|
|
88
|
+
--force) FORCE=true ;;
|
|
89
|
+
--update) UPDATE_MODE=true ;;
|
|
90
|
+
*) warn "Unknown flag: $arg"; show_usage; exit 1 ;;
|
|
91
|
+
esac
|
|
90
92
|
done
|
|
91
|
-
if [ ${#missing[@]} -gt 0 ]; then
|
|
92
|
-
echo -e " ${RED}✗${NC} missing required commands: ${missing[*]}"
|
|
93
|
-
return 1
|
|
94
|
-
fi
|
|
95
|
-
return 0
|
|
96
93
|
}
|
|
97
94
|
|
|
98
|
-
# ──
|
|
99
|
-
section "Pre-flight: checking system tools"
|
|
95
|
+
# ── Dry-run guard ──────────────────────────────────────────────────────────────
|
|
100
96
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
# so the rest of this script can find them even if the calling shell
|
|
106
|
-
# didn't source ~/.profile first.
|
|
107
|
-
for p in "$HOME/.opencode/bin" "$HOME/.local/npm/bin" "$HOME/.cargo/bin"; do
|
|
108
|
-
case ":$PATH:" in
|
|
109
|
-
*":$p:"*) ;;
|
|
110
|
-
*) [ -d "$p" ] && export PATH="$p:$PATH" ;;
|
|
111
|
-
esac
|
|
112
|
-
done
|
|
113
|
-
|
|
114
|
-
MISSING=()
|
|
115
|
-
have_cmd node || MISSING+=("node")
|
|
116
|
-
have_cmd uv || MISSING+=("uv")
|
|
117
|
-
have_cmd uvx || MISSING+=("uvx")
|
|
118
|
-
have_cmd "$PYTHON_BIN" || MISSING+=("$PYTHON_BIN")
|
|
119
|
-
have_cmd jq || MISSING+=("jq")
|
|
120
|
-
have_cmd npx || MISSING+=("npx")
|
|
121
|
-
have_cmd git || MISSING+=("git")
|
|
122
|
-
have_cmd bun || MISSING+=("bun")
|
|
123
|
-
have_cmd gh || MISSING+=("gh")
|
|
124
|
-
have_cmd opencode || MISSING+=("opencode")
|
|
125
|
-
have_cmd sqlite3 || MISSING+=("sqlite3")
|
|
126
|
-
have_cmd make || MISSING+=("make")
|
|
127
|
-
have_cmd g++ || MISSING+=("g++")
|
|
128
|
-
|
|
129
|
-
# Chrome detection: prefer chrome-headless-shell from puppeteer cache, then
|
|
130
|
-
# system chromium / chrome / google-chrome. We don't require Chrome — the
|
|
131
|
-
# installer works without it — but we report it so the operator knows.
|
|
132
|
-
CHROME_BIN=""
|
|
133
|
-
for cand in \
|
|
134
|
-
"$HOME/.cache/puppeteer/chrome-headless-shell"*/chrome-headless-shell/chrome-headless-shell \
|
|
135
|
-
/usr/bin/chromium \
|
|
136
|
-
/usr/bin/chrome \
|
|
137
|
-
/usr/bin/google-chrome \
|
|
138
|
-
"$(command -v chromium 2>/dev/null || true)" \
|
|
139
|
-
"$(command -v chrome 2>/dev/null || true)" \
|
|
140
|
-
"$(command -v google-chrome 2>/dev/null || true)"; do
|
|
141
|
-
[ -n "$cand" ] && [ -x "$cand" ] && CHROME_BIN="$cand" && break
|
|
142
|
-
done
|
|
143
|
-
|
|
144
|
-
for t in "${MISSING[@]}"; do warn "missing system tool: $t"; done
|
|
145
|
-
if [ -z "$CHROME_BIN" ]; then warn "no chrome / chromium found — browser-harness won't work until installed"; fi
|
|
146
|
-
[ ${#MISSING[@]} -eq 0 ] && [ -n "$CHROME_BIN" ] && note "all system tools present"
|
|
147
|
-
|
|
148
|
-
# ── Install missing system tools (best effort, never abort) ─────────────
|
|
149
|
-
section "Install missing system tools"
|
|
150
|
-
|
|
151
|
-
# uv (Python package manager — needed for browser-harness + Semble)
|
|
152
|
-
if ! have_cmd uv; then
|
|
153
|
-
echo -e " ${CYAN}→${NC} Installing uv (Python package manager)..."
|
|
154
|
-
if have_cmd curl; then
|
|
155
|
-
if curl -LsSf https://astral.sh/uv/install.sh | sh >/dev/null 2>&1; then
|
|
156
|
-
# uv's installer drops the binary at ~/.local/bin/uv
|
|
157
|
-
export PATH="$HOME/.local/bin:$PATH"
|
|
158
|
-
if have_cmd uv; then
|
|
159
|
-
note "uv installed at $(command -v uv)"
|
|
160
|
-
else
|
|
161
|
-
warn "uv install ran but uv not on PATH — try 'export PATH=\$HOME/.local/bin:\$PATH'"
|
|
162
|
-
fi
|
|
163
|
-
else
|
|
164
|
-
warn "uv installer script failed — install manually: https://docs.astral.sh/uv/"
|
|
165
|
-
fi
|
|
166
|
-
else
|
|
167
|
-
warn "curl not found — install uv manually: https://docs.astral.sh/uv/"
|
|
97
|
+
dry() {
|
|
98
|
+
if $DRY_RUN; then
|
|
99
|
+
dim " [DRY-RUN] $*"
|
|
100
|
+
return 0
|
|
168
101
|
fi
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
# is available — uv can fetch its own python.
|
|
176
|
-
if ! have_cmd "$PYTHON_BIN"; then
|
|
177
|
-
if have_cmd uv; then
|
|
178
|
-
echo -e " ${CYAN}→${NC} python3.12 not on PATH — uv will fetch on first use"
|
|
179
|
-
note "python3.12 will be auto-fetched by uv (browser-harness will trigger first fetch)"
|
|
102
|
+
"$@"
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
sudo_if_needed() {
|
|
106
|
+
if [ "$EUID" -eq 0 ]; then
|
|
107
|
+
dry "$@"
|
|
180
108
|
else
|
|
181
|
-
|
|
109
|
+
dry sudo "$@"
|
|
182
110
|
fi
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
;;
|
|
199
|
-
Darwin)
|
|
200
|
-
if have_cmd brew; then brew install jq 2>/dev/null && note "jq installed (brew)" || warn "brew install jq failed — install manually"
|
|
201
|
-
else warn "no brew — install jq manually"
|
|
202
|
-
fi
|
|
203
|
-
;;
|
|
204
|
-
*) warn "unknown OS — install jq manually";;
|
|
205
|
-
esac
|
|
206
|
-
else
|
|
207
|
-
note "jq $(jq --version)"
|
|
208
|
-
fi
|
|
209
|
-
|
|
210
|
-
# Chrome (best effort — install chrome-headless-shell from chrome-for-testing
|
|
211
|
-
# or the puppeteer cache). The dashboard/browser-harness will work without
|
|
212
|
-
# it (the user can install later), but we try.
|
|
213
|
-
if [ -z "$CHROME_BIN" ]; then
|
|
214
|
-
echo -e " ${CYAN}→${NC} Trying to install chrome-headless-shell..."
|
|
215
|
-
PUPPETEER_CACHE_DIR="$HOME/.cache/puppeteer"
|
|
216
|
-
mkdir -p "$PUPPETEER_CACHE_DIR"
|
|
217
|
-
# chrome-for-testing JSON API — find the latest stable chrome-headless-shell
|
|
218
|
-
# URL for linux64. Fall back to nothing if the request fails.
|
|
219
|
-
CHROME_CT_URL=$(curl -fsSL "https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions-with-downloads.json" 2>/dev/null \
|
|
220
|
-
| grep -o '"url": *"[^"]*chrome-headless-shell-linux64[^"]*"' \
|
|
221
|
-
| head -1 \
|
|
222
|
-
| sed 's/.*"url": *"\([^"]*\)".*/\1/')
|
|
223
|
-
if [ -n "$CHROME_CT_URL" ]; then
|
|
224
|
-
TMP_DIR=$(mktemp -d)
|
|
225
|
-
if curl -fsSL "$CHROME_CT_URL" -o "$TMP_DIR/chrome-headless-shell.zip" 2>/dev/null; then
|
|
226
|
-
TARGET_DIR="$PUPPETEER_CACHE_DIR/chrome-headless-shell"
|
|
227
|
-
mkdir -p "$TARGET_DIR"
|
|
228
|
-
unzip -q -o "$TMP_DIR/chrome-headless-shell.zip" -d "$TARGET_DIR"
|
|
229
|
-
CHROME_BIN=$(find "$TARGET_DIR" -name chrome-headless-shell -type f 2>/dev/null | head -1)
|
|
230
|
-
[ -n "$CHROME_BIN" ] && [ -x "$CHROME_BIN" ] && note "chrome-headless-shell installed at $CHROME_BIN" || warn "chrome-headless-shell install failed"
|
|
231
|
-
rm -rf "$TMP_DIR"
|
|
232
|
-
else
|
|
233
|
-
warn "download from chrome-for-testing failed — install Chrome manually"
|
|
234
|
-
fi
|
|
235
|
-
else
|
|
236
|
-
warn "could not resolve latest chrome-for-testing URL — install Chrome manually"
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
# ── Shared arrays (populated by check_deps, consumed by install_missing_deps) ──
|
|
114
|
+
MISSING_DEPS=()
|
|
115
|
+
MISSING_CMDS=()
|
|
116
|
+
|
|
117
|
+
# ── check_deps: run node scripts/check-deps.mjs and parse result ───────────────
|
|
118
|
+
|
|
119
|
+
check_deps() {
|
|
120
|
+
section "Checking dependencies"
|
|
121
|
+
action "Running dependency detector..."
|
|
122
|
+
local json
|
|
123
|
+
if ! json=$(node "$REPO_DIR/scripts/check-deps.mjs" --strict 2>/dev/null); then
|
|
124
|
+
warn "dependency detector failed — proceeding with best-effort checks"
|
|
125
|
+
return
|
|
237
126
|
fi
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
# we warn but continue (Chrome won't work, but the rest of the install
|
|
245
|
-
# will).
|
|
246
|
-
if [ -n "$CHROME_BIN" ] || [ -z "$CHROME_BIN" ]; then
|
|
247
|
-
# Check if chrome-headless-shell actually runs (libs satisfied)
|
|
248
|
-
if [ -n "$CHROME_BIN" ] && ! "$CHROME_BIN" --version >/dev/null 2>&1; then
|
|
249
|
-
case "$(uname -s 2>/dev/null || echo unknown)" in
|
|
250
|
-
Linux)
|
|
251
|
-
if have_cmd apt-get; then
|
|
252
|
-
echo -e " ${CYAN}→${NC} chrome-headless-shell needs runtime libs — installing via apt..."
|
|
253
|
-
if sudo -n apt-get install -y --no-install-recommends \
|
|
254
|
-
libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 \
|
|
255
|
-
libxdamage1 libxkbcommon0 libasound2t64 libatspi2.0-0 \
|
|
256
|
-
2>/dev/null; then
|
|
257
|
-
note "chrome runtime libs installed (libnss3, libnspr4, libatk*, libxkbcommon, libasound, libatspi)"
|
|
258
|
-
else
|
|
259
|
-
warn "apt install of chrome runtime libs failed (needs sudo). run manually:"
|
|
260
|
-
warn " sudo apt-get install -y libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 \\"
|
|
261
|
-
warn " libxdamage1 libxkbcommon0 libasound2t64 libatspi2.0-0"
|
|
262
|
-
fi
|
|
263
|
-
else
|
|
264
|
-
warn "chrome-headless-shell runtime libs missing — install libnss3, libnspr4, libatk* for your distro"
|
|
265
|
-
fi
|
|
266
|
-
;;
|
|
267
|
-
Darwin)
|
|
268
|
-
warn "chrome-headless-shell runtime libs missing — on macOS: brew install --cask chromium"
|
|
269
|
-
;;
|
|
270
|
-
*) warn "chrome-headless-shell runtime libs missing — install via your package manager";;
|
|
271
|
-
esac
|
|
127
|
+
|
|
128
|
+
local ok
|
|
129
|
+
ok=$(echo "$json" | node -e "console.log(JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')).ok)")
|
|
130
|
+
if [ "$ok" = "true" ]; then
|
|
131
|
+
note "all required dependencies satisfied"
|
|
132
|
+
return
|
|
272
133
|
fi
|
|
273
|
-
fi
|
|
274
134
|
|
|
275
|
-
#
|
|
276
|
-
|
|
135
|
+
# Print missing deps
|
|
136
|
+
while IFS='|' read -r name cmd; do
|
|
137
|
+
[ -z "$name" ] && continue
|
|
138
|
+
warn "missing dependency: $name"
|
|
139
|
+
if [ -n "$cmd" ]; then
|
|
140
|
+
MISSING_CMDS+=("$cmd")
|
|
141
|
+
fi
|
|
142
|
+
done < <(echo "$json" | node -e "
|
|
143
|
+
const d = JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));
|
|
144
|
+
for (const m of d.missing) {
|
|
145
|
+
if (m.name === 'tmux') continue;
|
|
146
|
+
console.log(m.name + '|' + (m.installCmd || ''));
|
|
147
|
+
}
|
|
148
|
+
")
|
|
149
|
+
}
|
|
277
150
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
note "native build deps installed (build-essential, python3, sqlite3-dev)"
|
|
284
|
-
else
|
|
285
|
-
warn "apt install of native build deps failed (needs sudo). Install manually:"
|
|
286
|
-
warn " sudo apt-get install -y build-essential python3 sqlite3 libsqlite3-dev"
|
|
151
|
+
# ── install_missing_deps: run install commands for missing deps ─────────────────
|
|
152
|
+
|
|
153
|
+
install_missing_deps() {
|
|
154
|
+
if [ ${#MISSING_CMDS[@]} -eq 0 ]; then
|
|
155
|
+
return
|
|
287
156
|
fi
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
if
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
if npm install -g skills >/dev/null 2>&1; then
|
|
299
|
-
note "skills CLI installed at $(command -v skills 2>/dev/null || echo 'npm global')"
|
|
300
|
-
else
|
|
301
|
-
warn "skills CLI install failed (run manually: npm install -g skills)"
|
|
157
|
+
|
|
158
|
+
section "Installing missing dependencies"
|
|
159
|
+
|
|
160
|
+
local i
|
|
161
|
+
for i in "${!MISSING_CMDS[@]}"; do
|
|
162
|
+
local cmd="${MISSING_CMDS[$i]}"
|
|
163
|
+
action "Running: $cmd"
|
|
164
|
+
if $DRY_RUN; then
|
|
165
|
+
dim " would execute: $cmd"
|
|
166
|
+
continue
|
|
302
167
|
fi
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
fi
|
|
306
|
-
else
|
|
307
|
-
note "skills CLI already installed"
|
|
308
|
-
fi
|
|
309
|
-
|
|
310
|
-
# ── Bun (JavaScript runtime, for plugin tests) ────────────────────────
|
|
311
|
-
section "Install Bun"
|
|
312
|
-
|
|
313
|
-
if ! have_cmd bun; then
|
|
314
|
-
echo -e " ${CYAN}→${NC} Installing Bun..."
|
|
315
|
-
if have_cmd curl; then
|
|
316
|
-
if curl -fsSL https://bun.sh/install | bash >/dev/null 2>&1; then
|
|
317
|
-
export PATH="$HOME/.bun/bin:$PATH"
|
|
318
|
-
if have_cmd bun; then
|
|
319
|
-
note "Bun installed at $(command -v bun)"
|
|
320
|
-
else
|
|
321
|
-
warn "Bun installer ran but bun not on PATH — try 'export PATH=\$HOME/.bun/bin:\$PATH'"
|
|
322
|
-
fi
|
|
168
|
+
if eval "$cmd" >/dev/null 2>&1; then
|
|
169
|
+
note "dependency installed"
|
|
323
170
|
else
|
|
324
|
-
warn "
|
|
171
|
+
warn "install command failed — you may need to install this manually"
|
|
325
172
|
fi
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
fi
|
|
329
|
-
else
|
|
330
|
-
note "Bun $(bun --version 2>/dev/null | head -1)"
|
|
331
|
-
fi
|
|
173
|
+
done
|
|
174
|
+
}
|
|
332
175
|
|
|
333
|
-
# ──
|
|
334
|
-
section "Install GitHub CLI"
|
|
176
|
+
# ── ensure_node: node MUST be available for the rest of the install ────────────
|
|
335
177
|
|
|
336
|
-
|
|
337
|
-
|
|
178
|
+
ensure_node() {
|
|
179
|
+
if have_cmd node; then return; fi
|
|
180
|
+
section "Node.js is required"
|
|
181
|
+
action "Install Node.js 18+ first, then re-run this script."
|
|
182
|
+
if $DRY_RUN; then
|
|
183
|
+
dim " would install nodejs via platform package manager"
|
|
184
|
+
return
|
|
185
|
+
fi
|
|
186
|
+
case "$(uname -s)" in
|
|
338
187
|
Linux)
|
|
339
188
|
if have_cmd apt-get; then
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
else
|
|
344
|
-
warn "apt install gh failed. Install manually: https://cli.github.com/"
|
|
345
|
-
fi
|
|
189
|
+
action "Detected apt — installing via NodeSource..."
|
|
190
|
+
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo bash -
|
|
191
|
+
sudo apt-get install -y nodejs
|
|
346
192
|
elif have_cmd dnf; then
|
|
347
|
-
|
|
193
|
+
action "Detected dnf — installing nodejs..."
|
|
194
|
+
sudo dnf install -y nodejs
|
|
348
195
|
elif have_cmd pacman; then
|
|
349
|
-
|
|
196
|
+
action "Detected pacman — installing nodejs..."
|
|
197
|
+
sudo pacman -S --noconfirm nodejs npm
|
|
198
|
+
elif have_cmd zypper; then
|
|
199
|
+
action "Detected zypper — installing nodejs..."
|
|
200
|
+
sudo zypper install -y nodejs20
|
|
350
201
|
else
|
|
351
|
-
|
|
202
|
+
err "No known package manager. Install Node.js manually: https://nodejs.org/"
|
|
203
|
+
exit 1
|
|
352
204
|
fi
|
|
353
205
|
;;
|
|
354
206
|
Darwin)
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
else
|
|
358
|
-
warn "no brew — install gh manually: https://cli.github.com/"
|
|
359
|
-
fi
|
|
207
|
+
action "Installing node via Homebrew..."
|
|
208
|
+
brew install node@18
|
|
360
209
|
;;
|
|
361
210
|
*)
|
|
362
|
-
|
|
211
|
+
err "Unsupported OS. Install Node.js manually: https://nodejs.org/"
|
|
212
|
+
exit 1
|
|
363
213
|
;;
|
|
364
214
|
esac
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
note "opencode CLI installed at $(command -v opencode)"
|
|
378
|
-
else
|
|
379
|
-
warn "opencode installer ran but binary not on PATH"
|
|
380
|
-
fi
|
|
381
|
-
else
|
|
382
|
-
warn "opencode installer failed — install manually: https://opencode.ai"
|
|
383
|
-
fi
|
|
384
|
-
else
|
|
385
|
-
warn "curl not found — install opencode manually: https://opencode.ai"
|
|
215
|
+
note "Node.js installed: $(node --version)"
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
# ── git operations (clone or update) ───────────────────────────────────────────
|
|
219
|
+
|
|
220
|
+
ensure_repo() {
|
|
221
|
+
if $UPDATE_MODE; then
|
|
222
|
+
section "Updating BizarHarness repository"
|
|
223
|
+
action "Pulling latest via git pull --ff-only..."
|
|
224
|
+
dry git -C "$REPO_DIR" pull --ff-only
|
|
225
|
+
note "repository updated"
|
|
226
|
+
return
|
|
386
227
|
fi
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
if have_cmd npm; then
|
|
395
|
-
for pkg in @polderlabs/bizar; do
|
|
396
|
-
if npm ls -g "$pkg" --depth=0 >/dev/null 2>&1; then
|
|
397
|
-
cur=$(npm ls -g "$pkg" --depth=0 --json 2>/dev/null | grep -oE '"version":\s*"[^"]+"' | head -1 | sed 's/.*"\([^"]*\)".*/\1/')
|
|
398
|
-
note "$pkg already installed (v$cur)"
|
|
399
|
-
else
|
|
400
|
-
echo -e " ${CYAN}→${NC} Installing $pkg..."
|
|
401
|
-
if npm install -g "$pkg" >/dev/null 2>&1; then
|
|
402
|
-
note "$pkg installed"
|
|
403
|
-
else
|
|
404
|
-
err "$pkg install failed (run manually: npm install -g $pkg)"
|
|
405
|
-
fi
|
|
406
|
-
fi
|
|
407
|
-
done
|
|
408
|
-
else
|
|
409
|
-
err "npm not found — install Node.js 20+ first: https://nodejs.org/"
|
|
410
|
-
fi
|
|
411
|
-
|
|
412
|
-
# ── browser-harness (Python tool via uv, requires Python 3.12) ─────────
|
|
413
|
-
section "Install browser-harness (Python via uv)"
|
|
414
|
-
|
|
415
|
-
if have_cmd browser-harness; then
|
|
416
|
-
note "browser-harness $(browser-harness --version 2>&1 | head -1) already installed"
|
|
417
|
-
else
|
|
418
|
-
if have_cmd uv; then
|
|
419
|
-
echo -e " ${CYAN}→${NC} uv tool install --python 3.12 --upgrade --force browser-harness"
|
|
420
|
-
if uv tool install --python 3.12 --upgrade --force browser-harness >/dev/null 2>&1; then
|
|
421
|
-
note "browser-harness installed"
|
|
422
|
-
else
|
|
423
|
-
err "browser-harness install failed (run manually: uv tool install --python 3.12 --upgrade --force browser-harness)"
|
|
228
|
+
|
|
229
|
+
# If we're already inside the repo, skip clone.
|
|
230
|
+
if [ -d "$REPO_DIR/.git" ]; then
|
|
231
|
+
note "repository already present at $REPO_DIR"
|
|
232
|
+
if $FORCE || $UPDATE_MODE; then
|
|
233
|
+
dry git -C "$REPO_DIR" pull --ff-only
|
|
424
234
|
fi
|
|
425
|
-
|
|
426
|
-
warn "uv not installed — skipping browser-harness (install uv first, then re-run this script)"
|
|
235
|
+
return
|
|
427
236
|
fi
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
note "
|
|
436
|
-
else
|
|
437
|
-
warn "browser-harness skill registration failed (re-run: browser-harness skill > $BH_SKILL_DIR/SKILL.md)"
|
|
438
|
-
fi
|
|
439
|
-
fi
|
|
440
|
-
|
|
441
|
-
# ── Chrome lifecycle (start browser-harness-up.sh) ────────────────────
|
|
442
|
-
section "Start Chrome for browser-harness"
|
|
443
|
-
|
|
444
|
-
if [ -x "$REPO_DIR/cli/browser-harness-up.sh" ]; then
|
|
445
|
-
# The script knows how to find chrome-headless-shell from the
|
|
446
|
-
# puppeteer cache (or any system chrome).
|
|
447
|
-
if BH_OUTPUT=$("$REPO_DIR/cli/browser-harness-up.sh" start 2>&1); then
|
|
448
|
-
# Trim verbose output; just keep the success lines.
|
|
449
|
-
while IFS= read -r line; do
|
|
450
|
-
case "$line" in
|
|
451
|
-
*"✓"*|*"already"*|*"stopped"*|*"restarted"*) note "$line" ;;
|
|
452
|
-
*) ;;
|
|
453
|
-
esac
|
|
454
|
-
done <<< "$BH_OUTPUT"
|
|
237
|
+
|
|
238
|
+
section "Cloning BizarHarness repository"
|
|
239
|
+
local target
|
|
240
|
+
target="$(dirname "$REPO_DIR")/BizarHarness"
|
|
241
|
+
if [ ! -d "$target" ]; then
|
|
242
|
+
action "Cloning into $target..."
|
|
243
|
+
dry git clone https://github.com/DrB0rk/BizarHarness.git "$target"
|
|
244
|
+
note "cloned"
|
|
455
245
|
else
|
|
456
|
-
|
|
457
|
-
fi
|
|
458
|
-
elif have_cmd "$REPO_DIR/../bin/bizar"; then
|
|
459
|
-
if "$REPO_DIR/../bin/bizar" browser-harness-up start 2>&1 | grep -E '✓|✗' | head -3; then
|
|
460
|
-
note "Chrome started via bizar browser-harness-up"
|
|
246
|
+
note "already cloned at $target"
|
|
461
247
|
fi
|
|
462
|
-
|
|
463
|
-
warn "cli/browser-harness-up.sh not found at $REPO_DIR — skipping Chrome auto-start"
|
|
464
|
-
fi
|
|
248
|
+
}
|
|
465
249
|
|
|
466
|
-
# ──
|
|
467
|
-
section "Install BizarHarness config files"
|
|
250
|
+
# ── Install agent/config files via cli/install.mjs ──────────────────────────────
|
|
468
251
|
|
|
469
|
-
|
|
252
|
+
install_config() {
|
|
253
|
+
section "Installing BizarHarness config files"
|
|
470
254
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
name="$(basename "$f")"
|
|
475
|
-
if [ -f "$CONFIG_DIR/agents/$name" ] && cmp -s "$f" "$CONFIG_DIR/agents/$name"; then
|
|
476
|
-
: # identical, skip silently
|
|
477
|
-
else
|
|
478
|
-
cp "$f" "$CONFIG_DIR/agents/$name"
|
|
479
|
-
INSTALLED+=("agents/$name")
|
|
255
|
+
if $DRY_RUN; then
|
|
256
|
+
dim " would run: node \"$REPO_DIR/cli/install.mjs\" --non-interactive"
|
|
257
|
+
return
|
|
480
258
|
fi
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
mkdir -p "$CONFIG_DIR/commands"
|
|
494
|
-
for f in "$REPO_DIR/config/commands/"*.md; do
|
|
495
|
-
[ -f "$f" ] || continue
|
|
496
|
-
name="$(basename "$f")"
|
|
497
|
-
cp "$f" "$CONFIG_DIR/commands/$name"
|
|
498
|
-
done
|
|
499
|
-
note "slash commands synced"
|
|
500
|
-
fi
|
|
501
|
-
|
|
502
|
-
# Hooks
|
|
503
|
-
if [ -d "$REPO_DIR/config/hooks/" ]; then
|
|
504
|
-
mkdir -p "$CONFIG_DIR/hooks"
|
|
505
|
-
for entry in "$REPO_DIR/config/hooks/"*; do
|
|
506
|
-
[ -e "$entry" ] || continue
|
|
507
|
-
name="$(basename "$entry")"
|
|
508
|
-
cp -R "$entry" "$CONFIG_DIR/hooks/$name"
|
|
509
|
-
done
|
|
510
|
-
note "hooks synced"
|
|
511
|
-
fi
|
|
512
|
-
|
|
513
|
-
# AGENTS.md
|
|
514
|
-
if [ -f "$REPO_DIR/config/AGENTS.md" ]; then
|
|
515
|
-
cp "$REPO_DIR/config/AGENTS.md" "$CONFIG_DIR/AGENTS.md"
|
|
516
|
-
note "AGENTS.md synced"
|
|
517
|
-
fi
|
|
518
|
-
|
|
519
|
-
# ── Bundled skills (bizar, self-improvement, agent-baseline, …) ───────
|
|
520
|
-
section "Install bundled skills"
|
|
521
|
-
|
|
522
|
-
mkdir -p "$SKILLS_DIR"
|
|
523
|
-
# Core skills — bundled from config/skills/ in the repo.
|
|
524
|
-
for skill in bizar self-improvement cpp-coding-standards cpp-testing embedded-esp-idf \
|
|
525
|
-
obsidian glyph read-the-damn-docs; do
|
|
526
|
-
if [ -d "$REPO_DIR/config/skills/$skill" ]; then
|
|
527
|
-
mkdir -p "$SKILLS_DIR/$skill"
|
|
528
|
-
cp -R "$REPO_DIR/config/skills/$skill/." "$SKILLS_DIR/$skill/"
|
|
529
|
-
note "skill: $skill"
|
|
259
|
+
|
|
260
|
+
if [ -f "$REPO_DIR/cli/install.mjs" ]; then
|
|
261
|
+
action "Running cli/install.mjs (agent files, skills, plugin)..."
|
|
262
|
+
|
|
263
|
+
# cli/install.mjs runInstaller() spawns bash install.sh — we need to pass
|
|
264
|
+
# the flags through. Use the canonical entry point with environment hints.
|
|
265
|
+
# The installer automatically copies agents, commands, hooks, skills, plugin.
|
|
266
|
+
if node "$REPO_DIR/cli/install.mjs" --non-interactive 2>&1; then
|
|
267
|
+
note "config files installed"
|
|
268
|
+
else
|
|
269
|
+
warn "cli/install.mjs exited non-zero — some config may be incomplete"
|
|
270
|
+
fi
|
|
530
271
|
else
|
|
531
|
-
|
|
532
|
-
mkdir -p "$SKILLS_DIR/$skill"
|
|
533
|
-
case "$skill" in
|
|
534
|
-
obsidian)
|
|
535
|
-
# v3.24.0 — Prefer the on-disk SKILL.md so the inline copy can't
|
|
536
|
-
# drift. Fall back to an inline heredoc ONLY when the repo source
|
|
537
|
-
# is missing (e.g. someone running install.sh outside a clone).
|
|
538
|
-
if [ -f "$REPO_DIR/config/skills/obsidian/SKILL.md" ]; then
|
|
539
|
-
cp "$REPO_DIR/config/skills/obsidian/SKILL.md" "$SKILLS_DIR/$skill/SKILL.md"
|
|
540
|
-
note "skill: $skill (from repo)"
|
|
541
|
-
else
|
|
542
|
-
cat > "$SKILLS_DIR/$skill/SKILL.md" << 'OBSIDIAN_SKILL'
|
|
543
|
-
---
|
|
544
|
-
name: obsidian
|
|
545
|
-
description: Interact with the Obsidian vault for persistent per-project memory. Use when searching notes, creating notes, writing wikilinks, using callouts, setting properties, or managing the project's obsidian knowledge bank.
|
|
546
|
-
---
|
|
547
|
-
|
|
548
|
-
# Obsidian Vault
|
|
549
|
-
|
|
550
|
-
Per-project persistent memory for Bizar agents. Every project gets its own vault bank, keyed by project name.
|
|
551
|
-
|
|
552
|
-
## Operations
|
|
553
|
-
|
|
554
|
-
- **Search vault**: `obsidian_search` with `bank_id: "<project-name>"` — use before writing code that might have prior context
|
|
555
|
-
- **Create/update notes**: `obsidian_write` with bank_id — store decisions, ADRs, architecture notes
|
|
556
|
-
- **Append to notes**: `obsidian_append` — add to existing notes
|
|
557
|
-
- **List notes**: `obsidian_list_notes` with optional directory prefix
|
|
558
|
-
|
|
559
|
-
## Conventions
|
|
560
|
-
|
|
561
|
-
- Use `bank_id: "<project-name>"` determined from the project root (the directory name)
|
|
562
|
-
- Use wikilinks `[[Note Name]]` for cross-references within the vault
|
|
563
|
-
- Use frontmatter for structured properties (tags, status, date)
|
|
564
|
-
- Store ADRs under `adr/` prefix
|
|
565
|
-
- Store implementation notes under `notes/` prefix
|
|
566
|
-
OBSIDIAN_SKILL
|
|
567
|
-
note "skill: $skill (inline)"
|
|
568
|
-
fi
|
|
569
|
-
;;
|
|
570
|
-
glyph)
|
|
571
|
-
cat > "$SKILLS_DIR/$skill/SKILL.md" << 'GLYPH_SKILL'
|
|
572
|
-
---
|
|
573
|
-
name: glyph
|
|
574
|
-
description: Work with the project knowledge graph (graphify). Query the graph before searching source files for structural and relationship questions. Use for architecture discovery, dependency mapping, and cross-module understanding.
|
|
575
|
-
---
|
|
576
|
-
|
|
577
|
-
# Glyph — Knowledge Graph
|
|
578
|
-
|
|
579
|
-
Graph-based codebase navigation using graphify (`.bizar/graph/`). Maps source files, functions, types, and their relationships into a queryable graph with community detection.
|
|
580
|
-
|
|
581
|
-
## Commands
|
|
582
|
-
|
|
583
|
-
```bash
|
|
584
|
-
bizar graph status # node/edge/community counts
|
|
585
|
-
bizar graph query <term> # BFS traversal from a concept
|
|
586
|
-
bizar graph path <A> <B> # shortest path between two concepts
|
|
587
|
-
bizar graph explain <X> # all nodes related to X
|
|
588
|
-
bizar graph update # incremental rebuild
|
|
589
|
-
bizar graph build # full rebuild
|
|
590
|
-
```
|
|
591
|
-
|
|
592
|
-
## When to Use
|
|
593
|
-
|
|
594
|
-
- Before reading a large file: `bizar graph explain "<module>"`
|
|
595
|
-
- Mapping unfamiliar code: `bizar graph query "<feature>"`
|
|
596
|
-
- Debugging cross-module: `bizar graph path "<symptom>" "<root-cause>"`
|
|
597
|
-
- Before grep: check the graph first — may point directly to the file
|
|
598
|
-
GLYPH_SKILL
|
|
599
|
-
note "skill: $skill (inline)"
|
|
600
|
-
;;
|
|
601
|
-
read-the-damn-docs)
|
|
602
|
-
cat > "$SKILLS_DIR/$skill/SKILL.md" << 'RTDD_SKILL'
|
|
603
|
-
---
|
|
604
|
-
name: read-the-damn-docs
|
|
605
|
-
description: Before writing code, read the official documentation. Use websearch/webfetch to find docs for any API, library, framework, or tool. Never guess API signatures, parameter names, or return types.
|
|
606
|
-
---
|
|
607
|
-
|
|
608
|
-
# Read the Damn Docs
|
|
609
|
-
|
|
610
|
-
Discipline: always check official documentation before writing code against an unfamiliar API.
|
|
611
|
-
|
|
612
|
-
## Rules
|
|
613
|
-
|
|
614
|
-
1. **Search docs first**: Before using any function/API/component you haven't used recently, `websearch` or `webfetch` the official docs
|
|
615
|
-
2. **No guessing**: Never invent parameter names, return types, or method signatures from training data alone
|
|
616
|
-
3. **Read error messages**: When something fails, read the full error and search docs for the exact error text
|
|
617
|
-
4. **Version matters**: Note the library/tool version and use docs matching that version
|
|
618
|
-
5. **Check examples**: Most good docs have worked examples — find and follow them
|
|
619
|
-
|
|
620
|
-
## When to Apply
|
|
621
|
-
|
|
622
|
-
- Adding a new dependency — read its install/setup docs
|
|
623
|
-
- Using an unfamiliar API method — check signature and return type
|
|
624
|
-
- Debugging an error you don't understand — search docs + error message
|
|
625
|
-
- Configuring a tool — read the official config reference
|
|
626
|
-
RTDD_SKILL
|
|
627
|
-
note "skill: $skill (inline)"
|
|
628
|
-
;;
|
|
629
|
-
*)
|
|
630
|
-
warn "skill source missing: $skill (no inline fallback)"
|
|
631
|
-
;;
|
|
632
|
-
esac
|
|
272
|
+
warn "cli/install.mjs not found — config files not auto-installed"
|
|
633
273
|
fi
|
|
634
|
-
|
|
274
|
+
}
|
|
635
275
|
|
|
636
|
-
#
|
|
637
|
-
if [ -f "$REPO_DIR/config/agents/_shared/AGENT_BASELINE.md" ]; then
|
|
638
|
-
mkdir -p "$SKILLS_DIR/agent-baseline"
|
|
639
|
-
cp "$REPO_DIR/config/agents/_shared/AGENT_BASELINE.md" "$SKILLS_DIR/agent-baseline/SKILL.md"
|
|
640
|
-
note "skill: agent-baseline (shared by all 14 agents)"
|
|
641
|
-
fi
|
|
276
|
+
# ── Verify opencode.json has the Bizar plugin registered ───────────────────────
|
|
642
277
|
|
|
643
|
-
|
|
644
|
-
|
|
278
|
+
verify_plugin() {
|
|
279
|
+
section "Verifying Bizar plugin registration"
|
|
645
280
|
|
|
646
|
-
|
|
647
|
-
section "Install domain skills from skills.sh"
|
|
281
|
+
local opencode_config="$CONFIG_DIR/opencode.json"
|
|
648
282
|
|
|
649
|
-
if
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
note "impeccable (UI anti-pattern detector)"
|
|
653
|
-
else
|
|
654
|
-
warn "impeccable install failed (run: npx impeccable skills install)"
|
|
283
|
+
if [ ! -f "$opencode_config" ]; then
|
|
284
|
+
warn "opencode.json not found at $opencode_config — plugin not registered"
|
|
285
|
+
return
|
|
655
286
|
fi
|
|
656
287
|
|
|
657
|
-
#
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
288
|
+
# Check if plugin entry exists using node's JSON parser (avoid jq dependency)
|
|
289
|
+
local has_plugin
|
|
290
|
+
has_plugin=$(node -e "
|
|
291
|
+
const cfg = JSON.parse(require('fs').readFileSync('$opencode_config', 'utf8'));
|
|
292
|
+
const plugins = cfg.plugin || [];
|
|
293
|
+
const found = plugins.some(function(p) {
|
|
294
|
+
return Array.isArray(p) && p[0] && p[0].includes('plugins/bizar');
|
|
295
|
+
});
|
|
296
|
+
console.log(found ? 'yes' : 'no');
|
|
297
|
+
" 2>/dev/null || echo "no")
|
|
298
|
+
|
|
299
|
+
if [ "$has_plugin" = "yes" ]; then
|
|
300
|
+
note "Bizar plugin registered in opencode.json"
|
|
301
|
+
return
|
|
662
302
|
fi
|
|
663
303
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
304
|
+
action "Adding Bizar plugin entry to opencode.json..."
|
|
305
|
+
|
|
306
|
+
if $DRY_RUN; then
|
|
307
|
+
dim " would patch opencode.json to add plugin entry"
|
|
308
|
+
return
|
|
669
309
|
fi
|
|
670
|
-
else
|
|
671
|
-
warn "npx not found — skipping domain skill installation"
|
|
672
|
-
fi
|
|
673
310
|
|
|
674
|
-
#
|
|
675
|
-
|
|
311
|
+
# Patch via node script (no jq dependency needed)
|
|
312
|
+
node -e "
|
|
313
|
+
const fs = require('fs');
|
|
314
|
+
const path = '$opencode_config';
|
|
315
|
+
let cfg;
|
|
316
|
+
try {
|
|
317
|
+
cfg = JSON.parse(fs.readFileSync(path, 'utf8'));
|
|
318
|
+
} catch(e) {
|
|
319
|
+
console.error('Failed to read opencode.json:', e.message);
|
|
320
|
+
process.exit(1);
|
|
321
|
+
}
|
|
322
|
+
if (!cfg.plugin) cfg.plugin = [];
|
|
323
|
+
const has = cfg.plugin.some(function(p) {
|
|
324
|
+
return Array.isArray(p) && p[0] && p[0].includes('plugins/bizar');
|
|
325
|
+
});
|
|
326
|
+
if (!has) {
|
|
327
|
+
cfg.plugin.push(['./plugins/bizar/index.ts', {
|
|
328
|
+
loopThresholdWarn: 5,
|
|
329
|
+
loopThresholdEscalate: 8,
|
|
330
|
+
loopThresholdBlock: 12,
|
|
331
|
+
loopWindowSize: 10
|
|
332
|
+
}]);
|
|
333
|
+
}
|
|
334
|
+
fs.writeFileSync(path, JSON.stringify(cfg, null, 2) + '\n', 'utf8');
|
|
335
|
+
console.log(has ? 'already registered' : 'plugin entry added');
|
|
336
|
+
"
|
|
337
|
+
|
|
338
|
+
note "opencode.json updated with Bizar plugin"
|
|
339
|
+
}
|
|
676
340
|
|
|
677
|
-
|
|
341
|
+
# ── Install background service ─────────────────────────────────────────────────
|
|
678
342
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
#
|
|
686
|
-
# v3.20.12: two sources, in priority order:
|
|
687
|
-
# 1. Local repo's plugins/bizar/ (for `git clone` users)
|
|
688
|
-
# 2. Globally installed @polderlabs/bizar-plugin (for `npm i -g` users)
|
|
689
|
-
# The npm-installed plugin also needs its bundled node_modules/ copied
|
|
690
|
-
# alongside (it depends on @polderlabs/bizar-sdk, which isn't on npm).
|
|
691
|
-
# This previously lived in cli/install.mjs:installPluginFromGlobal();
|
|
692
|
-
# duplicating the logic here makes install.sh the single source of truth.
|
|
693
|
-
PLUGIN_SRC=""
|
|
694
|
-
if [ -d "$REPO_DIR/plugins/bizar" ]; then
|
|
695
|
-
PLUGIN_SRC="$REPO_DIR/plugins/bizar"
|
|
696
|
-
PLUGIN_SRC_KIND="local repo"
|
|
697
|
-
elif have_cmd npm; then
|
|
698
|
-
# Resolve the npm global plugin path. `npm root -g` gives us
|
|
699
|
-
# /usr/local/lib/node_modules; the plugin lives in @polderlabs/bizar-plugin/.
|
|
700
|
-
NPM_GLOBAL_ROOT=$(npm root -g 2>/dev/null || echo "")
|
|
701
|
-
if [ -n "$NPM_GLOBAL_ROOT" ] && [ -d "$NPM_GLOBAL_ROOT/@polderlabs/bizar-plugin" ]; then
|
|
702
|
-
PLUGIN_SRC="$NPM_GLOBAL_ROOT/@polderlabs/bizar-plugin"
|
|
703
|
-
PLUGIN_SRC_KIND="@polderlabs/bizar-plugin (global)"
|
|
704
|
-
fi
|
|
705
|
-
fi
|
|
706
|
-
|
|
707
|
-
if [ -n "$PLUGIN_SRC" ]; then
|
|
708
|
-
mkdir -p "$PLUGIN_DST"
|
|
709
|
-
while IFS= read -r -d '' f; do
|
|
710
|
-
rel="${f#$PLUGIN_SRC/}"
|
|
711
|
-
case "$rel" in
|
|
712
|
-
node_modules/*|dist/*|*.log|.DS_Store) continue ;;
|
|
713
|
-
esac
|
|
714
|
-
mkdir -p "$(dirname "$PLUGIN_DST/$rel")"
|
|
715
|
-
cp "$f" "$PLUGIN_DST/$rel"
|
|
716
|
-
done < <(find "$PLUGIN_SRC" -type f -print0)
|
|
717
|
-
note "plugins/bizar/ copied from $PLUGIN_SRC_KIND"
|
|
718
|
-
# v4.0.0: No bundled node_modules copy. The SDK ships inside the same
|
|
719
|
-
# @polderlabs/bizar package, so plugin imports resolve from opencode's
|
|
720
|
-
# own node_modules at runtime.
|
|
721
|
-
else
|
|
722
|
-
warn "Bizar plugin source not found (install @polderlabs/bizar: npm i -g @polderlabs/bizar)"
|
|
723
|
-
fi
|
|
724
|
-
|
|
725
|
-
# ── Merge opencode.json (template + user's existing config) ────────────
|
|
726
|
-
section "Configure opencode.json"
|
|
727
|
-
|
|
728
|
-
# Prefer the .template copy; fall back to the legacy tracked file.
|
|
729
|
-
if [ -f "$REPO_DIR/config/opencode.json.template" ]; then
|
|
730
|
-
TEMPLATE="$REPO_DIR/config/opencode.json.template"
|
|
731
|
-
elif [ -f "$REPO_DIR/config/opencode.json" ]; then
|
|
732
|
-
TEMPLATE="$REPO_DIR/config/opencode.json"
|
|
733
|
-
else
|
|
734
|
-
warn "No opencode.json template found"
|
|
735
|
-
TEMPLATE=""
|
|
736
|
-
fi
|
|
737
|
-
|
|
738
|
-
if [ -n "$TEMPLATE" ]; then
|
|
739
|
-
if [ -f "$CONFIG_DIR/opencode.json" ]; then
|
|
740
|
-
cp "$CONFIG_DIR/opencode.json" "$CONFIG_DIR/opencode.json.bak"
|
|
343
|
+
install_service() {
|
|
344
|
+
section "Installing background service"
|
|
345
|
+
|
|
346
|
+
if $DRY_RUN; then
|
|
347
|
+
dim " would run: node \"$REPO_DIR/scripts/install-service.mjs\""
|
|
348
|
+
return
|
|
741
349
|
fi
|
|
742
350
|
|
|
743
|
-
if
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
351
|
+
if [ -f "$REPO_DIR/scripts/install-service.mjs" ]; then
|
|
352
|
+
local result
|
|
353
|
+
result=$(node "$REPO_DIR/scripts/install-service.mjs" 2>&1) || true
|
|
354
|
+
# Parse the JSON result
|
|
355
|
+
local ok
|
|
356
|
+
ok=$(echo "$result" | node -e "
|
|
357
|
+
try {
|
|
358
|
+
const r = JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));
|
|
359
|
+
console.log(r.ok !== false ? 'yes' : 'no');
|
|
360
|
+
} catch(e) { console.log('no'); }
|
|
361
|
+
" 2>/dev/null <<< "$result" || echo "no")
|
|
362
|
+
|
|
363
|
+
local skipped
|
|
364
|
+
skipped=$(echo "$result" | node -e "
|
|
365
|
+
try {
|
|
366
|
+
const r = JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));
|
|
367
|
+
console.log(r.skipped ? 'yes' : 'no');
|
|
368
|
+
} catch(e) { console.log('no'); }
|
|
369
|
+
" 2>/dev/null <<< "$result" || echo "no")
|
|
370
|
+
|
|
371
|
+
if [ "$ok" = "yes" ]; then
|
|
372
|
+
if [ "$skipped" = "yes" ]; then
|
|
373
|
+
dim " (service-controller not yet available — will be completed when Stream A lands)"
|
|
374
|
+
fi
|
|
375
|
+
note "service registration complete"
|
|
749
376
|
else
|
|
750
|
-
|
|
377
|
+
warn "service registration had issues — see output above"
|
|
378
|
+
dim " manual command: node cli/service-controller.mjs install"
|
|
751
379
|
fi
|
|
752
|
-
|
|
753
|
-
# Ensure the Bizar plugin entry is registered (idempotent).
|
|
754
|
-
PLUGIN_TMP="$CONFIG_DIR/opencode.json.tmp.$$"
|
|
755
|
-
jq '
|
|
756
|
-
if (.plugin // []) | map(.[0] == "./plugins/bizar/index.ts") | any then .
|
|
757
|
-
else .plugin = (.plugin // []) + [["./plugins/bizar/index.ts", {
|
|
758
|
-
"loopThresholdWarn": 5,
|
|
759
|
-
"loopThresholdEscalate": 8,
|
|
760
|
-
"loopThresholdBlock": 12,
|
|
761
|
-
"loopWindowSize": 10
|
|
762
|
-
}]]
|
|
763
|
-
end
|
|
764
|
-
' "$CONFIG_DIR/opencode.json" > "$PLUGIN_TMP" \
|
|
765
|
-
&& mv "$PLUGIN_TMP" "$CONFIG_DIR/opencode.json" \
|
|
766
|
-
|| rm -f "$PLUGIN_TMP"
|
|
767
|
-
note "opencode.json (template merged with existing config; plugin entry ensured)"
|
|
768
380
|
else
|
|
769
|
-
|
|
770
|
-
warn "jq not available — copied template directly. Re-install jq for safe merging."
|
|
381
|
+
warn "scripts/install-service.mjs not found"
|
|
771
382
|
fi
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
# ──
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
if
|
|
781
|
-
|
|
782
|
-
--memory-repo-name "$MEMORY_REPO_NAME" \
|
|
783
|
-
--yes 2>/dev/null; then
|
|
784
|
-
note "memory service ready (mode: managed, repo: ~/.local/share/bizar/memory/$MEMORY_REPO_NAME)"
|
|
785
|
-
note "next steps:"
|
|
786
|
-
note " bizar memory status # check vault + git state"
|
|
787
|
-
note " bizar memory sync # pull, validate, scan, commit, push"
|
|
788
|
-
note " bizar memory doctor # health check"
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
# ── Verify .opencode/plugins/bizar exists ──────────────────────────────────────
|
|
386
|
+
|
|
387
|
+
verify_plugin_dir() {
|
|
388
|
+
section "Verifying plugin directory"
|
|
389
|
+
|
|
390
|
+
local dst_plugin="${XDG_CONFIG_HOME:-$HOME/.config}/opencode/plugins/bizar"
|
|
391
|
+
if [ -d "$dst_plugin" ]; then
|
|
392
|
+
note "plugin directory exists at $dst_plugin"
|
|
789
393
|
else
|
|
790
|
-
|
|
394
|
+
action "Setting up plugin directory..."
|
|
395
|
+
dry mkdir -p "$dst_plugin"
|
|
396
|
+
# Copy from repo if available
|
|
397
|
+
if [ -d "$REPO_DIR/plugins/bizar" ]; then
|
|
398
|
+
if ! $DRY_RUN; then
|
|
399
|
+
cp -R "$REPO_DIR/plugins/bizar/." "$dst_plugin/"
|
|
400
|
+
fi
|
|
401
|
+
note "plugin files copied from repo"
|
|
402
|
+
else
|
|
403
|
+
warn "no plugin source found in repo — run 'bizar install' or 'npm install -g @polderlabs/bizar-plugin'"
|
|
404
|
+
fi
|
|
791
405
|
fi
|
|
792
|
-
else
|
|
793
|
-
warn "node not on PATH — skipping memory service bootstrap"
|
|
794
|
-
fi
|
|
795
|
-
|
|
796
|
-
# ── Write install-state.json (used by `bizar update` for migrations) ───
|
|
797
|
-
section "Write install-state"
|
|
798
|
-
|
|
799
|
-
mkdir -p "$BIZAR_STATE_DIR"
|
|
800
|
-
BH_VERSION=$(browser-harness --version 2>/dev/null | head -1 | sed 's/[^0-9.]//g' || echo "")
|
|
801
|
-
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 "")
|
|
802
|
-
PY_VERSION=$("$PYTHON_BIN" --version 2>/dev/null | head -1 | sed 's/[^0-9.]//g' || echo "")
|
|
803
|
-
UV_VERSION=$(uv --version 2>/dev/null | head -1 | sed 's/[^0-9.]//g' || echo "")
|
|
804
|
-
JQ_VERSION=$(jq --version 2>/dev/null | sed 's/[^0-9.]//g' || echo "")
|
|
805
|
-
|
|
806
|
-
STATE_JSON=$(cat <<JSON
|
|
807
|
-
{
|
|
808
|
-
"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]*//')",
|
|
809
|
-
"components": {
|
|
810
|
-
"bizar": "${BIZAR_VERSION:-unknown}",
|
|
811
|
-
"browser-harness": "${BH_VERSION:-unknown}",
|
|
812
|
-
"uv": "${UV_VERSION:-unknown}",
|
|
813
|
-
"python3.12": "${PY_VERSION:-unknown}",
|
|
814
|
-
"jq": "${JQ_VERSION:-unknown}"
|
|
815
|
-
}
|
|
816
406
|
}
|
|
817
|
-
JSON
|
|
818
|
-
)
|
|
819
|
-
echo "$STATE_JSON" > "$BIZAR_STATE_FILE"
|
|
820
|
-
note "install-state written to $BIZAR_STATE_FILE"
|
|
821
|
-
|
|
822
|
-
# ── Final dependency verification ────────────────────────────────────
|
|
823
|
-
section "Verify all dependencies"
|
|
824
|
-
|
|
825
|
-
if verify_dependencies; then
|
|
826
|
-
note "all required tools available on PATH"
|
|
827
|
-
else
|
|
828
|
-
err "some required tools are missing — see list above"
|
|
829
|
-
fi
|
|
830
|
-
|
|
831
|
-
# ── Final status banner ──────────────────────────────────────────────
|
|
832
|
-
section "Install complete"
|
|
833
|
-
|
|
834
|
-
echo ""
|
|
835
|
-
echo -e "${BOLD}${CYAN}┌────────────────────────────────────────────────────────────┐${NC}"
|
|
836
|
-
echo -e "${BOLD}${CYAN}│${NC} ${BOLD}BizarHarness ready.${NC} │"
|
|
837
|
-
echo -e "${BOLD}${CYAN}│${NC} │"
|
|
838
|
-
|
|
839
|
-
if [ ${#INSTALLED[@]} -gt 0 ]; then
|
|
840
|
-
echo -e "${BOLD}${CYAN}│${NC} ${GREEN}✓ Installed:${NC} │"
|
|
841
|
-
for entry in "${INSTALLED[@]}"; do
|
|
842
|
-
short=$(echo "$entry" | head -c 56)
|
|
843
|
-
echo -e "${BOLD}${CYAN}│${NC} • $short$(printf '%*s' $((56 - ${#short})) '') │"
|
|
844
|
-
done
|
|
845
|
-
fi
|
|
846
407
|
|
|
847
|
-
|
|
408
|
+
# ── Final success banner ───────────────────────────────────────────────────────
|
|
409
|
+
|
|
410
|
+
print_banner() {
|
|
411
|
+
local dashboard_url="http://localhost:3333"
|
|
412
|
+
section "Install complete"
|
|
413
|
+
|
|
414
|
+
echo ""
|
|
415
|
+
echo -e "${BOLD}${CYAN}┌────────────────────────────────────────────────────────────┐${NC}"
|
|
416
|
+
echo -e "${BOLD}${CYAN}│${NC} ${BOLD}BizarHarness ready.${NC} │"
|
|
848
417
|
echo -e "${BOLD}${CYAN}│${NC} │"
|
|
849
|
-
echo -e "${BOLD}${CYAN}│${NC} ${
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
418
|
+
echo -e "${BOLD}${CYAN}│${NC} ${GREEN}✓${NC} Cross-platform installer v3.22.0 │"
|
|
419
|
+
echo -e "${BOLD}${CYAN}│${NC} │"
|
|
420
|
+
echo -e "${BOLD}${CYAN}│${NC} Dashboard: ${CYAN}$dashboard_url${NC} │"
|
|
421
|
+
echo -e "${BOLD}${CYAN}│${NC} │"
|
|
422
|
+
echo -e "${BOLD}${CYAN}│${NC} ${DIM}Next:${NC} │"
|
|
423
|
+
echo -e "${BOLD}${CYAN}│${NC} ${DIM} 1. Restart opencode to pick up new config${NC} │"
|
|
424
|
+
echo -e "${BOLD}${CYAN}│${NC} ${DIM} 2. Run /connect in opencode to add API keys${NC} │"
|
|
425
|
+
echo -e "${BOLD}${CYAN}│${NC} ${DIM} 3. Run 'bizar dash start' to launch the dashboard${NC} │"
|
|
426
|
+
echo -e "${BOLD}${CYAN}│${NC} ${DIM} 4. Visit $dashboard_url in your browser${NC} │"
|
|
427
|
+
echo -e "${BOLD}${CYAN}│${NC} │"
|
|
428
|
+
echo -e "${BOLD}${CYAN}└────────────────────────────────────────────────────────────┘${NC}"
|
|
429
|
+
echo ""
|
|
430
|
+
|
|
431
|
+
if $DRY_RUN; then
|
|
432
|
+
warn "DRY RUN — no changes were made"
|
|
433
|
+
fi
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
# ── install_linux: Debian/Ubuntu, Fedora/RHEL, Arch, openSUSE ──────────────────
|
|
437
|
+
|
|
438
|
+
install_linux() {
|
|
439
|
+
note "Detected Linux ($(uname -m))"
|
|
440
|
+
|
|
441
|
+
ensure_node
|
|
442
|
+
|
|
443
|
+
# Pre-flight: populate PATH with common locations
|
|
444
|
+
for p in "$HOME/.opencode/bin" "$HOME/.local/bin" "$HOME/.bun/bin"; do
|
|
445
|
+
case ":$PATH:" in *":$p:"*) ;; *) [ -d "$p" ] && export PATH="$p:$PATH" ;; esac
|
|
853
446
|
done
|
|
854
|
-
fi
|
|
855
447
|
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
448
|
+
check_deps
|
|
449
|
+
install_missing_deps
|
|
450
|
+
|
|
451
|
+
ensure_repo
|
|
452
|
+
install_config
|
|
453
|
+
verify_plugin_dir
|
|
454
|
+
verify_plugin
|
|
455
|
+
install_service
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
# ── install_macos: Homebrew-based ──────────────────────────────────────────────
|
|
459
|
+
|
|
460
|
+
install_macos() {
|
|
461
|
+
note "Detected macOS ($(uname -m))"
|
|
462
|
+
|
|
463
|
+
# Ensure Homebrew
|
|
464
|
+
if ! have_cmd brew; then
|
|
465
|
+
section "Homebrew"
|
|
466
|
+
action "Installing Homebrew..."
|
|
467
|
+
if $DRY_RUN; then
|
|
468
|
+
dim " would install Homebrew via /bin/bash -c '\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)'"
|
|
469
|
+
else
|
|
470
|
+
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
|
471
|
+
note "Homebrew installed"
|
|
472
|
+
fi
|
|
473
|
+
else
|
|
474
|
+
note "Homebrew already installed"
|
|
475
|
+
fi
|
|
476
|
+
|
|
477
|
+
ensure_node
|
|
478
|
+
|
|
479
|
+
# Pre-flight: populate PATH
|
|
480
|
+
for p in "$HOME/.opencode/bin" "$HOME/.local/bin" "$HOME/.bun/bin" "/opt/homebrew/bin"; do
|
|
481
|
+
case ":$PATH:" in *":$p:"*) ;; *) [ -d "$p" ] && export PATH="$p:$PATH" ;; esac
|
|
862
482
|
done
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
483
|
+
|
|
484
|
+
check_deps
|
|
485
|
+
install_missing_deps
|
|
486
|
+
|
|
487
|
+
ensure_repo
|
|
488
|
+
install_config
|
|
489
|
+
verify_plugin_dir
|
|
490
|
+
verify_plugin
|
|
491
|
+
install_service
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
# ── Main entry ──────────────────────────────────────────────────────────────────
|
|
495
|
+
|
|
496
|
+
main() {
|
|
497
|
+
parse_flags "$@"
|
|
498
|
+
|
|
499
|
+
if $DRY_RUN; then
|
|
500
|
+
dim "DRY RUN MODE — no changes will be made"
|
|
501
|
+
echo ""
|
|
502
|
+
fi
|
|
503
|
+
|
|
504
|
+
# Header
|
|
505
|
+
echo ""
|
|
506
|
+
echo -e "${BOLD}${CYAN} ⚡ BizarHarness Installer v3.22.0${NC}"
|
|
507
|
+
if $UPDATE_MODE; then echo -e " ${DIM}Update mode${NC}"; fi
|
|
508
|
+
echo ""
|
|
509
|
+
|
|
510
|
+
# OS detection
|
|
511
|
+
local os
|
|
512
|
+
os="$(uname -s)"
|
|
513
|
+
case "$os" in
|
|
514
|
+
Linux)
|
|
515
|
+
install_linux
|
|
516
|
+
;;
|
|
517
|
+
Darwin)
|
|
518
|
+
install_macos
|
|
519
|
+
;;
|
|
520
|
+
*)
|
|
521
|
+
echo ""
|
|
522
|
+
err "Unsupported OS: $os"
|
|
523
|
+
echo ""
|
|
524
|
+
warn "On Windows, run install.ps1 instead."
|
|
525
|
+
warn "On other Unix systems, ensure Node.js 18+ is available and run:"
|
|
526
|
+
warn " node scripts/check-deps.mjs"
|
|
527
|
+
echo ""
|
|
528
|
+
exit 1
|
|
529
|
+
;;
|
|
530
|
+
esac
|
|
531
|
+
|
|
532
|
+
print_banner
|
|
533
|
+
exit 0
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
main "$@"
|