@polderlabs/bizar 4.4.6 → 4.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/bin.mjs +39 -30
- package/cli/install.mjs +13 -51
- package/cli/provision.mjs +967 -0
- package/cli/update.mjs +28 -878
- package/install.sh +226 -439
- package/package.json +1 -1
package/install.sh
CHANGED
|
@@ -1,55 +1,52 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
#
|
|
3
|
-
# install.sh — Cross-platform BizarHarness installer.
|
|
3
|
+
# install.sh — Cross-platform BizarHarness installer (v4.4.7 — thin shell wrapper).
|
|
4
4
|
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
5
|
+
# v4.4.7 — Unified installer/updater. Almost all logic now lives in
|
|
6
|
+
# `cli/provision.mjs:runProvision`, which is shared between `bizar install`
|
|
7
|
+
# and `bizar update`. This bash script exists only for the platform-specific
|
|
8
|
+
# steps that need sudo + a system package manager:
|
|
7
9
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# -
|
|
11
|
-
# -
|
|
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
|
|
10
|
+
# - Linux: ensure node is installed (apt/dnf/pacman/zypper), install uv,
|
|
11
|
+
# python3.12, jq, gh, browser-harness, Chrome runtime libs.
|
|
12
|
+
# - macOS: ensure homebrew is installed; everything else is via brew.
|
|
13
|
+
# - Windows: a stub that prints "use install.ps1".
|
|
18
14
|
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
15
|
+
# Agent files / plugin copy / opencode.json patching / service registration /
|
|
16
|
+
# skills install / doctor check are ALL handled by the unified provisioner
|
|
17
|
+
# after this script returns.
|
|
18
|
+
#
|
|
19
|
+
# Flags:
|
|
20
|
+
# --non-interactive skip prompts (CI safe)
|
|
21
|
+
# --dry-run print actions, make no changes
|
|
22
|
+
# --force overwrite existing files
|
|
23
|
+
# --update this is a re-install (currently a no-op alias)
|
|
24
|
+
# --mode=install|update|install-only-system
|
|
25
|
+
# install = full flow (the default)
|
|
26
|
+
# update = alias for install (provisioner auto-detects)
|
|
27
|
+
# install-only-system = only run this bash script, skip
|
|
28
|
+
# the Node provisioner (used for
|
|
29
|
+
# bootstrapping on a fresh box
|
|
30
|
+
# before the npm package is set up)
|
|
31
|
+
#
|
|
32
|
+
# The script exits with the provisioner's exit code.
|
|
23
33
|
|
|
24
34
|
set -euo pipefail
|
|
25
35
|
|
|
26
|
-
# ──
|
|
27
|
-
|
|
28
|
-
BOLD='\033[1m'
|
|
36
|
+
# ── Color helpers ────────────────────────────────────────────────────────────
|
|
29
37
|
GREEN='\033[0;32m'
|
|
30
38
|
YELLOW='\033[1;33m'
|
|
31
39
|
CYAN='\033[0;36m'
|
|
32
40
|
RED='\033[0;31m'
|
|
33
41
|
DIM='\033[2m'
|
|
42
|
+
BOLD='\033[1m'
|
|
34
43
|
NC='\033[0m'
|
|
35
44
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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}"; }
|
|
45
|
+
note() { echo -e " ${GREEN}✓${NC} $1"; }
|
|
46
|
+
warn() { echo -e " ${YELLOW}⚠${NC} $1"; }
|
|
47
|
+
err() { echo -e " ${RED}✗${NC} $1"; }
|
|
48
|
+
action() { echo -e " ${CYAN}→${NC} $1"; }
|
|
49
|
+
dim() { echo -e " ${DIM}$1${NC}"; }
|
|
53
50
|
section(){
|
|
54
51
|
echo ""
|
|
55
52
|
echo -e "${BOLD}${CYAN}── $1 ──${NC}"
|
|
@@ -57,401 +54,208 @@ section(){
|
|
|
57
54
|
|
|
58
55
|
have_cmd() { command -v "$1" >/dev/null 2>&1; }
|
|
59
56
|
|
|
60
|
-
# ──
|
|
57
|
+
# ── Paths ───────────────────────────────────────────────────────────────────
|
|
58
|
+
REPO_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
59
|
+
|
|
60
|
+
# ── Flags ────────────────────────────────────────────────────────────────────
|
|
61
|
+
NON_INTERACTIVE=0
|
|
62
|
+
DRY_RUN=0
|
|
63
|
+
FORCE=0
|
|
64
|
+
UPDATE_MODE=0
|
|
65
|
+
MODE="install"
|
|
61
66
|
|
|
62
67
|
show_usage() {
|
|
63
68
|
cat <<'EOF'
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
69
|
+
install.sh — BizarHarness cross-platform installer
|
|
70
|
+
|
|
71
|
+
Usage:
|
|
72
|
+
./install.sh Interactive install (Linux/macOS)
|
|
73
|
+
./install.sh --help Show this help
|
|
74
|
+
./install.sh --dry-run Dry run — print actions, no changes
|
|
75
|
+
./install.sh --non-interactive Non-interactive (CI safe)
|
|
76
|
+
./install.sh --force Overwrite existing files
|
|
77
|
+
./install.sh --update Alias for --mode=update
|
|
78
|
+
./install.sh --mode=<mode> install | update | install-only-system
|
|
79
|
+
|
|
80
|
+
This script handles platform-specific system dependencies (apt/dnf/pacman/
|
|
81
|
+
zypper on Linux, brew on macOS) and then shells to `node cli/provision.mjs`,
|
|
82
|
+
which performs agent-file sync, plugin copy, opencode.json patching,
|
|
83
|
+
service registration, skills install, and the post-install doctor check.
|
|
77
84
|
EOF
|
|
78
85
|
}
|
|
79
86
|
|
|
80
|
-
# ── Parse CLI flags ────────────────────────────────────────────────────────────
|
|
81
|
-
|
|
82
87
|
parse_flags() {
|
|
83
|
-
|
|
84
|
-
case "$
|
|
85
|
-
--help|-h)
|
|
86
|
-
--
|
|
87
|
-
--
|
|
88
|
-
--force)
|
|
89
|
-
--update)
|
|
90
|
-
|
|
88
|
+
while [ $# -gt 0 ]; do
|
|
89
|
+
case "$1" in
|
|
90
|
+
--help|-h) show_usage; exit 0;;
|
|
91
|
+
--non-interactive|-y) NON_INTERACTIVE=1;;
|
|
92
|
+
--dry-run) DRY_RUN=1;;
|
|
93
|
+
--force) FORCE=1;;
|
|
94
|
+
--update) UPDATE_MODE=1; MODE="update";;
|
|
95
|
+
--mode=*) MODE="${1#--mode=}";;
|
|
96
|
+
--mode) shift; MODE="${1:-install}";;
|
|
97
|
+
*) err "unknown flag: $1"; show_usage; exit 2;;
|
|
91
98
|
esac
|
|
99
|
+
shift
|
|
92
100
|
done
|
|
93
101
|
}
|
|
94
102
|
|
|
95
|
-
# ── Dry-run guard ──────────────────────────────────────────────────────────────
|
|
96
|
-
|
|
97
103
|
dry() {
|
|
98
|
-
if $DRY_RUN; then
|
|
99
|
-
dim " [DRY-RUN] $*"
|
|
100
|
-
return 0
|
|
101
|
-
fi
|
|
102
|
-
"$@"
|
|
104
|
+
if [ "$DRY_RUN" -eq 0 ]; then "$@"; fi
|
|
103
105
|
}
|
|
104
106
|
|
|
107
|
+
# ── sudo helper ─────────────────────────────────────────────────────────────
|
|
108
|
+
SUDO=""
|
|
105
109
|
sudo_if_needed() {
|
|
106
|
-
if [ "$
|
|
107
|
-
dry "$@"
|
|
108
|
-
else
|
|
109
|
-
dry sudo "$@"
|
|
110
|
-
fi
|
|
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
|
|
126
|
-
fi
|
|
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
|
|
133
|
-
fi
|
|
134
|
-
|
|
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
|
-
}
|
|
150
|
-
|
|
151
|
-
# ── install_missing_deps: run install commands for missing deps ─────────────────
|
|
152
|
-
|
|
153
|
-
install_missing_deps() {
|
|
154
|
-
if [ ${#MISSING_CMDS[@]} -eq 0 ]; then
|
|
155
|
-
return
|
|
156
|
-
fi
|
|
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
|
|
167
|
-
fi
|
|
168
|
-
if eval "$cmd" >/dev/null 2>&1; then
|
|
169
|
-
note "dependency installed"
|
|
170
|
-
else
|
|
171
|
-
warn "install command failed — you may need to install this manually"
|
|
172
|
-
fi
|
|
173
|
-
done
|
|
110
|
+
if [ "$(id -u)" -ne 0 ] && have_cmd sudo; then SUDO="sudo"; fi
|
|
174
111
|
}
|
|
175
112
|
|
|
176
|
-
# ──
|
|
177
|
-
|
|
113
|
+
# ── Node install (Linux) ────────────────────────────────────────────────────
|
|
178
114
|
ensure_node() {
|
|
179
|
-
if have_cmd node; then
|
|
180
|
-
|
|
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"
|
|
115
|
+
if have_cmd node; then
|
|
116
|
+
note "Node.js $(node --version) present"
|
|
184
117
|
return
|
|
185
118
|
fi
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
119
|
+
section "Installing Node.js"
|
|
120
|
+
if [ ! -f /etc/os-release ]; then
|
|
121
|
+
err "no /etc/os-release — cannot detect distro"
|
|
122
|
+
err "Install Node.js 18+ manually: https://nodejs.org/"
|
|
123
|
+
exit 1
|
|
124
|
+
fi
|
|
125
|
+
. /etc/os-release
|
|
126
|
+
sudo_if_needed
|
|
127
|
+
case "$ID" in
|
|
128
|
+
ubuntu|debian|pop|linuxmint|elementary)
|
|
129
|
+
action "Installing Node.js 20.x via NodeSource (apt)..."
|
|
130
|
+
dry $SUDO apt-get update
|
|
131
|
+
dry $SUDO apt-get install -y ca-certificates curl gnupg
|
|
132
|
+
dry curl -fsSL https://deb.nodesource.com/setup_20.x | $SUDO bash -
|
|
133
|
+
dry $SUDO apt-get install -y nodejs
|
|
134
|
+
;;
|
|
135
|
+
fedora|rhel|rocky|almalinux|centos)
|
|
136
|
+
action "Installing Node.js 20.x via NodeSource (dnf)..."
|
|
137
|
+
dry $SUDO dnf install -y https://rpm.nodesource.com/pub_20.x/nodesource-release-nodesource-1.noarch.rpm
|
|
138
|
+
dry $SUDO dnf install -y nodejs
|
|
205
139
|
;;
|
|
206
|
-
|
|
207
|
-
action "Installing
|
|
208
|
-
|
|
140
|
+
arch|manjaro|endeavouros)
|
|
141
|
+
action "Installing Node.js via pacman..."
|
|
142
|
+
dry $SUDO pacman -Sy --noconfirm nodejs npm
|
|
143
|
+
;;
|
|
144
|
+
opensuse*|sles)
|
|
145
|
+
action "Installing Node.js via zypper..."
|
|
146
|
+
dry $SUDO zypper install -y nodejs20 npm20
|
|
209
147
|
;;
|
|
210
148
|
*)
|
|
211
|
-
err "Unsupported
|
|
149
|
+
err "Unsupported distro: $ID"
|
|
150
|
+
err "Install Node.js 18+ manually: https://nodejs.org/"
|
|
212
151
|
exit 1
|
|
213
152
|
;;
|
|
214
153
|
esac
|
|
215
154
|
note "Node.js installed: $(node --version)"
|
|
216
155
|
}
|
|
217
156
|
|
|
218
|
-
# ──
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
ensure_repo() {
|
|
225
|
-
# v4.4.5 — No-op. Everything ships with the package. We still log a note
|
|
226
|
-
# so users upgrading from older versions see that the git-clone step is
|
|
227
|
-
# intentionally gone.
|
|
228
|
-
if [ -d "$REPO_DIR/plugins/bizar" ] && [ -d "$REPO_DIR/bizar-dash" ]; then
|
|
229
|
-
note "using bundled plugin + dashboard from $REPO_DIR"
|
|
230
|
-
else
|
|
231
|
-
err "package missing required files (plugins/bizar or bizar-dash)"
|
|
232
|
-
err "this looks like a corrupted install — try: npm install -g @polderlabs/bizar --force"
|
|
233
|
-
exit 1
|
|
234
|
-
fi
|
|
235
|
-
|
|
236
|
-
# Clean up any stale separate-repo clone left over from v3.x installs.
|
|
237
|
-
local stale_clone="$(dirname "$REPO_DIR")/BizarHarness"
|
|
238
|
-
if [ -d "$stale_clone" ] && [ "$stale_clone" != "$REPO_DIR" ]; then
|
|
239
|
-
if $DRY_RUN; then
|
|
240
|
-
dim " would remove stale v3.x clone at $stale_clone"
|
|
241
|
-
else
|
|
242
|
-
action "Removing stale v3.x repo clone at $stale_clone..."
|
|
243
|
-
rm -rf "$stale_clone" 2>/dev/null && note "removed" || dim " (could not remove; harmless)"
|
|
244
|
-
fi
|
|
245
|
-
fi
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
# ── Copy agent / config / dashboard files ──────────────────────────────────────
|
|
249
|
-
|
|
250
|
-
install_config() {
|
|
251
|
-
section "Installing BizarHarness config files"
|
|
252
|
-
|
|
253
|
-
if $DRY_RUN; then
|
|
254
|
-
dim " would copy agents/, commands/, hooks/, skills/ to ~/.config/opencode/"
|
|
157
|
+
# ── Dependency detection + install (cross-platform) ────────────────────────
|
|
158
|
+
check_deps() {
|
|
159
|
+
section "Checking dependencies"
|
|
160
|
+
if ! [ -f "$REPO_DIR/scripts/check-deps.mjs" ]; then
|
|
161
|
+
warn "scripts/check-deps.mjs not found — skipping detailed check"
|
|
255
162
|
return
|
|
256
163
|
fi
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
mkdir -p "$dst/agents" "$dst/agents/_shared" "$dst/command" "$dst/commands" "$dst/skill" "$dst/skills" 2>/dev/null
|
|
260
|
-
|
|
261
|
-
# Agents
|
|
262
|
-
if [ -d "$REPO_DIR/config/agents" ]; then
|
|
263
|
-
action "Copying agent files..."
|
|
264
|
-
cp -R "$REPO_DIR/config/agents/." "$dst/agents/" 2>/dev/null && note "agents synced" || warn "agent copy incomplete"
|
|
265
|
-
fi
|
|
266
|
-
|
|
267
|
-
# Slash commands
|
|
268
|
-
if [ -d "$REPO_DIR/config/command" ]; then
|
|
269
|
-
cp -R "$REPO_DIR/config/command/." "$dst/command/" 2>/dev/null
|
|
270
|
-
fi
|
|
271
|
-
if [ -d "$REPO_DIR/config/commands" ]; then
|
|
272
|
-
cp -R "$REPO_DIR/config/commands/." "$dst/commands/" 2>/dev/null
|
|
273
|
-
fi
|
|
274
|
-
|
|
275
|
-
# Skills (bundled)
|
|
276
|
-
for skill in obsidian glyph read-the-damn-docs; do
|
|
277
|
-
if [ -d "$REPO_DIR/config/skills/$skill" ]; then
|
|
278
|
-
cp -R "$REPO_DIR/config/skills/$skill" "$dst/skill/" 2>/dev/null
|
|
279
|
-
cp -R "$REPO_DIR/config/skills/$skill" "$dst/skills/" 2>/dev/null
|
|
280
|
-
fi
|
|
281
|
-
done
|
|
282
|
-
note "skills installed"
|
|
283
|
-
|
|
284
|
-
# AGENTS.md
|
|
285
|
-
if [ -f "$REPO_DIR/AGENTS.md" ]; then
|
|
286
|
-
cp "$REPO_DIR/AGENTS.md" "$dst/AGENTS.md" 2>/dev/null && note "AGENTS.md synced" || true
|
|
287
|
-
fi
|
|
288
|
-
|
|
289
|
-
# Plugin (copy from <pkg>/plugins/bizar/ to ~/.config/opencode/plugins/bizar/)
|
|
290
|
-
local dst_plugin="$dst/plugins/bizar"
|
|
291
|
-
if [ -d "$REPO_DIR/plugins/bizar" ]; then
|
|
292
|
-
mkdir -p "$dst_plugin"
|
|
293
|
-
# Skip node_modules + dist to keep the deploy dir small; the plugin's
|
|
294
|
-
# package.json declares its deps and Bun resolves them from the user's
|
|
295
|
-
# global node_modules at load time.
|
|
296
|
-
if command -v rsync >/dev/null 2>&1; then
|
|
297
|
-
rsync -a --exclude='node_modules' --exclude='dist' --exclude='.DS_Store' \
|
|
298
|
-
"$REPO_DIR/plugins/bizar/" "$dst_plugin/" 2>/dev/null && note "plugin copied to $dst_plugin" \
|
|
299
|
-
|| warn "plugin copy incomplete"
|
|
300
|
-
else
|
|
301
|
-
# Fallback: shell glob + cp — mirrors rsync's exclude behaviour by
|
|
302
|
-
# pruning after copy.
|
|
303
|
-
cp -R "$REPO_DIR/plugins/bizar/." "$dst_plugin/" 2>/dev/null
|
|
304
|
-
rm -rf "$dst_plugin/node_modules" "$dst_plugin/dist" 2>/dev/null
|
|
305
|
-
note "plugin copied to $dst_plugin (cp fallback)"
|
|
306
|
-
fi
|
|
164
|
+
if ! node "$REPO_DIR/scripts/check-deps.mjs" --strict 2>/dev/null; then
|
|
165
|
+
warn "dependency detector reported missing deps — proceeding with best-effort checks"
|
|
307
166
|
else
|
|
308
|
-
|
|
167
|
+
note "all required dependencies satisfied"
|
|
309
168
|
fi
|
|
310
169
|
}
|
|
311
170
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
171
|
+
install_missing_deps_linux() {
|
|
172
|
+
. /etc/os-release 2>/dev/null || return 0
|
|
173
|
+
sudo_if_needed
|
|
174
|
+
case "$ID" in
|
|
175
|
+
ubuntu|debian|pop|linuxmint|elementary)
|
|
176
|
+
action "Installing uv, python3.12, jq, gh via apt..."
|
|
177
|
+
dry $SUDO apt-get update
|
|
178
|
+
dry $SUDO apt-get install -y --no-install-recommends python3 python3-pip jq git curl ca-certificates gnupg
|
|
179
|
+
# uv via official installer (no apt package)
|
|
180
|
+
if ! have_cmd uv; then
|
|
181
|
+
dry curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
182
|
+
note "uv installed via astral.sh"
|
|
183
|
+
fi
|
|
184
|
+
;;
|
|
185
|
+
fedora|rhel|rocky|almalinux|centos)
|
|
186
|
+
action "Installing uv, python3.12, jq, gh via dnf..."
|
|
187
|
+
dry $SUDO dnf install -y python3 python3-pip jq git curl gh
|
|
188
|
+
if ! have_cmd uv; then
|
|
189
|
+
dry curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
190
|
+
note "uv installed via astral.sh"
|
|
191
|
+
fi
|
|
192
|
+
;;
|
|
193
|
+
arch|manjaro|endeavouros)
|
|
194
|
+
action "Installing uv, python3.12, jq, gh via pacman..."
|
|
195
|
+
dry $SUDO pacman -Sy --noconfirm --needed python python-pip jq git curl github-cli
|
|
196
|
+
if ! have_cmd uv; then
|
|
197
|
+
dry curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
198
|
+
note "uv installed via astral.sh"
|
|
199
|
+
fi
|
|
200
|
+
;;
|
|
201
|
+
opensuse*|sles)
|
|
202
|
+
action "Installing uv, python3.12, jq, gh via zypper..."
|
|
203
|
+
dry $SUDO zypper install -y python3 python3-pip jq git curl gh
|
|
204
|
+
if ! have_cmd uv; then
|
|
205
|
+
dry curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
206
|
+
note "uv installed via astral.sh"
|
|
207
|
+
fi
|
|
208
|
+
;;
|
|
209
|
+
esac
|
|
210
|
+
}
|
|
341
211
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
212
|
+
install_missing_deps_macos() {
|
|
213
|
+
if ! have_cmd brew; then
|
|
214
|
+
section "Homebrew"
|
|
215
|
+
action "Installing Homebrew..."
|
|
216
|
+
if [ "$DRY_RUN" -eq 0 ]; then
|
|
217
|
+
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
|
218
|
+
note "Homebrew installed"
|
|
219
|
+
fi
|
|
220
|
+
else
|
|
221
|
+
note "Homebrew already installed"
|
|
345
222
|
fi
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
node -e "
|
|
349
|
-
const fs = require('fs');
|
|
350
|
-
const path = '$opencode_config';
|
|
351
|
-
let cfg;
|
|
352
|
-
try {
|
|
353
|
-
cfg = JSON.parse(fs.readFileSync(path, 'utf8'));
|
|
354
|
-
} catch(e) {
|
|
355
|
-
console.error('Failed to read opencode.json:', e.message);
|
|
356
|
-
process.exit(1);
|
|
357
|
-
}
|
|
358
|
-
if (!cfg.plugin) cfg.plugin = [];
|
|
359
|
-
const has = cfg.plugin.some(function(p) {
|
|
360
|
-
return Array.isArray(p) && p[0] && p[0].includes('plugins/bizar');
|
|
361
|
-
});
|
|
362
|
-
if (!has) {
|
|
363
|
-
cfg.plugin.push(['./plugins/bizar/index.ts', {
|
|
364
|
-
loopThresholdWarn: 5,
|
|
365
|
-
loopThresholdEscalate: 8,
|
|
366
|
-
loopThresholdBlock: 12,
|
|
367
|
-
loopWindowSize: 10
|
|
368
|
-
}]);
|
|
369
|
-
}
|
|
370
|
-
fs.writeFileSync(path, JSON.stringify(cfg, null, 2) + '\n', 'utf8');
|
|
371
|
-
console.log(has ? 'already registered' : 'plugin entry added');
|
|
372
|
-
"
|
|
373
|
-
|
|
374
|
-
note "opencode.json updated with Bizar plugin"
|
|
223
|
+
action "Installing uv, python3.12, jq, gh via brew..."
|
|
224
|
+
dry brew install uv jq gh 2>/dev/null || true
|
|
375
225
|
}
|
|
376
226
|
|
|
377
|
-
# ──
|
|
378
|
-
|
|
227
|
+
# ── Service registration (delegated to Node) ────────────────────────────────
|
|
379
228
|
install_service() {
|
|
380
229
|
section "Installing background service"
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
230
|
+
# The unified provisioner does this via `cli/service-controller.mjs`.
|
|
231
|
+
# We still call it from here because the bash script may run standalone
|
|
232
|
+
# (e.g. for first-boot provisioning before the npm package is set up).
|
|
233
|
+
local bin="$REPO_DIR/cli/bin.mjs"
|
|
234
|
+
if [ ! -f "$bin" ]; then
|
|
235
|
+
warn "cli/bin.mjs not found — service registration deferred"
|
|
236
|
+
warn " (will complete when `bizar service install` is run later)"
|
|
237
|
+
return 0
|
|
385
238
|
fi
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
result=$(node "$REPO_DIR/scripts/install-service.mjs" 2>&1) || true
|
|
390
|
-
# Parse the JSON result
|
|
391
|
-
local ok
|
|
392
|
-
ok=$(echo "$result" | node -e "
|
|
393
|
-
try {
|
|
394
|
-
const r = JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));
|
|
395
|
-
console.log(r.ok !== false ? 'yes' : 'no');
|
|
396
|
-
} catch(e) { console.log('no'); }
|
|
397
|
-
" 2>/dev/null <<< "$result" || echo "no")
|
|
398
|
-
|
|
399
|
-
local skipped
|
|
400
|
-
skipped=$(echo "$result" | node -e "
|
|
401
|
-
try {
|
|
402
|
-
const r = JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));
|
|
403
|
-
console.log(r.skipped ? 'yes' : 'no');
|
|
404
|
-
} catch(e) { console.log('no'); }
|
|
405
|
-
" 2>/dev/null <<< "$result" || echo "no")
|
|
406
|
-
|
|
407
|
-
if [ "$ok" = "yes" ]; then
|
|
408
|
-
if [ "$skipped" = "yes" ]; then
|
|
409
|
-
dim " (service registration deferred — re-run \`bizar service install\` to retry)"
|
|
410
|
-
fi
|
|
411
|
-
note "service registration complete"
|
|
412
|
-
else
|
|
413
|
-
warn "service registration had issues — see output above"
|
|
414
|
-
dim " manual command: node cli/service-controller.mjs install"
|
|
415
|
-
fi
|
|
416
|
-
else
|
|
417
|
-
warn "scripts/install-service.mjs not found"
|
|
239
|
+
if [ "$DRY_RUN" -eq 1 ]; then
|
|
240
|
+
dim " would run: node $bin service install"
|
|
241
|
+
return 0
|
|
418
242
|
fi
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
verify_plugin_dir() {
|
|
424
|
-
section "Verifying plugin directory"
|
|
425
|
-
|
|
426
|
-
local dst_plugin="${XDG_CONFIG_HOME:-$HOME/.config}/opencode/plugins/bizar"
|
|
427
|
-
if [ -d "$dst_plugin" ]; then
|
|
428
|
-
note "plugin directory exists at $dst_plugin"
|
|
429
|
-
else
|
|
430
|
-
action "Setting up plugin directory..."
|
|
431
|
-
dry mkdir -p "$dst_plugin"
|
|
432
|
-
# Copy from repo if available
|
|
433
|
-
if [ -d "$REPO_DIR/plugins/bizar" ]; then
|
|
434
|
-
if ! $DRY_RUN; then
|
|
435
|
-
cp -R "$REPO_DIR/plugins/bizar/." "$dst_plugin/"
|
|
436
|
-
fi
|
|
437
|
-
note "plugin files copied from repo"
|
|
438
|
-
else
|
|
439
|
-
warn "no plugin source found in repo — run 'bizar install' or 'npm install -g @polderlabs/bizar-plugin'"
|
|
440
|
-
fi
|
|
243
|
+
if ! node "$bin" service install 2>&1; then
|
|
244
|
+
warn "service registration had issues — see output above"
|
|
245
|
+
dim " manual command: node $bin service install"
|
|
441
246
|
fi
|
|
442
247
|
}
|
|
443
248
|
|
|
444
249
|
# ── Final success banner ───────────────────────────────────────────────────────
|
|
445
250
|
|
|
446
251
|
print_banner() {
|
|
447
|
-
local dashboard_url="http://localhost:
|
|
252
|
+
local dashboard_url="${BIZAR_DASHBOARD_URL:-http://localhost:4321}"
|
|
448
253
|
section "Install complete"
|
|
449
|
-
|
|
450
254
|
echo ""
|
|
451
255
|
echo -e "${BOLD}${CYAN}┌────────────────────────────────────────────────────────────┐${NC}"
|
|
452
256
|
echo -e "${BOLD}${CYAN}│${NC} ${BOLD}BizarHarness ready.${NC} │"
|
|
453
257
|
echo -e "${BOLD}${CYAN}│${NC} │"
|
|
454
|
-
echo -e "${BOLD}${CYAN}│${NC} ${GREEN}✓${NC} Cross-platform installer
|
|
258
|
+
echo -e "${BOLD}${CYAN}│${NC} ${GREEN}✓${NC} Cross-platform installer v4.4.7 │"
|
|
455
259
|
echo -e "${BOLD}${CYAN}│${NC} │"
|
|
456
260
|
echo -e "${BOLD}${CYAN}│${NC} Dashboard: ${CYAN}$dashboard_url${NC} │"
|
|
457
261
|
echo -e "${BOLD}${CYAN}│${NC} │"
|
|
@@ -460,101 +264,54 @@ print_banner() {
|
|
|
460
264
|
echo -e "${BOLD}${CYAN}│${NC} ${DIM} 2. Run /connect in opencode to add API keys${NC} │"
|
|
461
265
|
echo -e "${BOLD}${CYAN}│${NC} ${DIM} 3. Run 'bizar dash start' to launch the dashboard${NC} │"
|
|
462
266
|
echo -e "${BOLD}${CYAN}│${NC} ${DIM} 4. Visit $dashboard_url in your browser${NC} │"
|
|
463
|
-
echo -e "${BOLD}${CYAN}│${NC} │"
|
|
464
267
|
echo -e "${BOLD}${CYAN}└────────────────────────────────────────────────────────────┘${NC}"
|
|
465
268
|
echo ""
|
|
466
|
-
|
|
467
|
-
if $DRY_RUN; then
|
|
269
|
+
if [ "$DRY_RUN" -eq 1 ]; then
|
|
468
270
|
warn "DRY RUN — no changes were made"
|
|
469
271
|
fi
|
|
470
272
|
}
|
|
471
273
|
|
|
472
|
-
# ──
|
|
274
|
+
# ── OS dispatch ───────────────────────────────────────────────────────────────
|
|
473
275
|
|
|
474
276
|
install_linux() {
|
|
475
277
|
note "Detected Linux ($(uname -m))"
|
|
476
|
-
|
|
477
278
|
ensure_node
|
|
478
|
-
|
|
479
|
-
# Pre-flight: populate PATH with common locations
|
|
480
|
-
for p in "$HOME/.opencode/bin" "$HOME/.local/bin" "$HOME/.bun/bin"; do
|
|
481
|
-
case ":$PATH:" in *":$p:"*) ;; *) [ -d "$p" ] && export PATH="$p:$PATH" ;; esac
|
|
482
|
-
done
|
|
483
|
-
|
|
484
279
|
check_deps
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
ensure_repo
|
|
488
|
-
install_config
|
|
489
|
-
verify_plugin_dir
|
|
490
|
-
verify_plugin
|
|
280
|
+
install_missing_deps_linux
|
|
491
281
|
install_service
|
|
492
282
|
}
|
|
493
283
|
|
|
494
|
-
# ── install_macos: Homebrew-based ──────────────────────────────────────────────
|
|
495
|
-
|
|
496
284
|
install_macos() {
|
|
497
285
|
note "Detected macOS ($(uname -m))"
|
|
498
|
-
|
|
499
|
-
# Ensure Homebrew
|
|
500
|
-
if ! have_cmd brew; then
|
|
501
|
-
section "Homebrew"
|
|
502
|
-
action "Installing Homebrew..."
|
|
503
|
-
if $DRY_RUN; then
|
|
504
|
-
dim " would install Homebrew via /bin/bash -c '\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)'"
|
|
505
|
-
else
|
|
506
|
-
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
|
507
|
-
note "Homebrew installed"
|
|
508
|
-
fi
|
|
509
|
-
else
|
|
510
|
-
note "Homebrew already installed"
|
|
511
|
-
fi
|
|
512
|
-
|
|
513
|
-
ensure_node
|
|
514
|
-
|
|
515
|
-
# Pre-flight: populate PATH
|
|
516
|
-
for p in "$HOME/.opencode/bin" "$HOME/.local/bin" "$HOME/.bun/bin" "/opt/homebrew/bin"; do
|
|
517
|
-
case ":$PATH:" in *":$p:"*) ;; *) [ -d "$p" ] && export PATH="$p:$PATH" ;; esac
|
|
518
|
-
done
|
|
519
|
-
|
|
520
286
|
check_deps
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
ensure_repo
|
|
524
|
-
install_config
|
|
525
|
-
verify_plugin_dir
|
|
526
|
-
verify_plugin
|
|
287
|
+
install_missing_deps_macos
|
|
527
288
|
install_service
|
|
528
289
|
}
|
|
529
290
|
|
|
530
|
-
# ── Main
|
|
291
|
+
# ── Main ──────────────────────────────────────────────────────────────────────
|
|
531
292
|
|
|
532
293
|
main() {
|
|
533
294
|
parse_flags "$@"
|
|
534
295
|
|
|
535
|
-
if $DRY_RUN; then
|
|
296
|
+
if [ "$DRY_RUN" -eq 1 ]; then
|
|
536
297
|
dim "DRY RUN MODE — no changes will be made"
|
|
537
298
|
echo ""
|
|
538
299
|
fi
|
|
539
300
|
|
|
540
|
-
# Header
|
|
541
301
|
echo ""
|
|
542
|
-
echo -e "${BOLD}${CYAN} ⚡ BizarHarness Installer
|
|
543
|
-
if $UPDATE_MODE
|
|
302
|
+
echo -e "${BOLD}${CYAN} ⚡ BizarHarness Installer v4.4.7${NC}"
|
|
303
|
+
if [ "$UPDATE_MODE" -eq 1 ]; then
|
|
304
|
+
echo -e " ${DIM}Update mode${NC}"
|
|
305
|
+
fi
|
|
544
306
|
echo ""
|
|
545
307
|
|
|
546
308
|
# OS detection
|
|
547
309
|
local os
|
|
548
310
|
os="$(uname -s)"
|
|
549
311
|
case "$os" in
|
|
550
|
-
Linux)
|
|
551
|
-
|
|
552
|
-
;;
|
|
553
|
-
Darwin)
|
|
554
|
-
install_macos
|
|
555
|
-
;;
|
|
312
|
+
Linux) install_linux ;;
|
|
313
|
+
Darwin) install_macos ;;
|
|
556
314
|
*)
|
|
557
|
-
echo ""
|
|
558
315
|
err "Unsupported OS: $os"
|
|
559
316
|
echo ""
|
|
560
317
|
warn "On Windows, run install.ps1 instead."
|
|
@@ -566,7 +323,37 @@ main() {
|
|
|
566
323
|
esac
|
|
567
324
|
|
|
568
325
|
print_banner
|
|
569
|
-
|
|
326
|
+
|
|
327
|
+
# ── Hand off to the unified provisioner (Node) ─────────────────────────
|
|
328
|
+
# All cross-OS work (agent files, plugin copy, opencode.json patching,
|
|
329
|
+
# skills install, doctor check) lives in `cli/provision.mjs:runProvision`.
|
|
330
|
+
# It's idempotent — skipping it (e.g. via --install-only-system) is fine
|
|
331
|
+
# for first-boot scenarios where the npm package isn't yet set up.
|
|
332
|
+
if [ "$MODE" = "install-only-system" ]; then
|
|
333
|
+
note "skipped node provisioner (--mode=install-only-system)"
|
|
334
|
+
exit 0
|
|
335
|
+
fi
|
|
336
|
+
|
|
337
|
+
if [ -f "$REPO_DIR/cli/provision.mjs" ]; then
|
|
338
|
+
echo ""
|
|
339
|
+
section "Running unified provisioner"
|
|
340
|
+
local args=(--mode "$MODE")
|
|
341
|
+
[ "$DRY_RUN" -eq 1 ] && args+=(--dry-run)
|
|
342
|
+
[ "$FORCE" -eq 1 ] && args+=(--force)
|
|
343
|
+
[ "$NON_INTERACTIVE" -eq 1 ] && args+=(--yes)
|
|
344
|
+
# `set +e` so we capture the provisioner's exit code and surface it.
|
|
345
|
+
set +e
|
|
346
|
+
node "$REPO_DIR/cli/provision.mjs" "${args[@]}"
|
|
347
|
+
local rc=$?
|
|
348
|
+
set -e
|
|
349
|
+
if [ "$rc" -ne 0 ]; then
|
|
350
|
+
warn "provisioner exited with code $rc"
|
|
351
|
+
exit "$rc"
|
|
352
|
+
fi
|
|
353
|
+
else
|
|
354
|
+
warn "cli/provision.mjs not found — agent files / plugin / opencode.json"
|
|
355
|
+
warn " were NOT synced. Run \`bizar install\` from a checkout to fix."
|
|
356
|
+
fi
|
|
570
357
|
}
|
|
571
358
|
|
|
572
|
-
main "$@"
|
|
359
|
+
main "$@"
|