@polderlabs/bizar 4.4.6 → 4.4.8

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 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
- # v3.22.0Rewritten for cross-platform dispatch. Linux (apt/dnf/pacman/zypper),
6
- # macOS (brew), and a pointer to install.ps1 for Windows.
5
+ # v4.4.7Unified 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
- # 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
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
- # What this script does NOT do (intentional):
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).
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
- # ── Constants ──────────────────────────────────────────────────────────────────
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
- REPO_DIR="$(cd "$(dirname "$0")" && pwd)"
37
- CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/opencode"
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}"; }
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
- # ── Usage ──────────────────────────────────────────────────────────────────────
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
- 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
-
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
- 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 ;;
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 [ "$EUID" -eq 0 ]; then
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
- # ── ensure_node: node MUST be available for the rest of the install ────────────
177
-
113
+ # ── Node install (Linux) ────────────────────────────────────────────────────
178
114
  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"
115
+ if have_cmd node; then
116
+ note "Node.js $(node --version) present"
184
117
  return
185
118
  fi
186
- case "$(uname -s)" in
187
- Linux)
188
- if have_cmd apt-get; then
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
192
- elif have_cmd dnf; then
193
- action "Detected dnf — installing nodejs..."
194
- sudo dnf install -y nodejs
195
- elif have_cmd pacman; then
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
201
- else
202
- err "No known package manager. Install Node.js manually: https://nodejs.org/"
203
- exit 1
204
- fi
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
- Darwin)
207
- action "Installing node via Homebrew..."
208
- brew install node@18
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 OS. Install Node.js manually: https://nodejs.org/"
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
- # ── Standalone installer (no git clone) ─────────────────────────────────────────
219
- # v4.4.5 — The plugin, agents, commands, hooks, and dashboard ALL ship inside
220
- # this package. No separate npm install, no git clone, no separate package
221
- # lookup. install.sh runs entirely against its own `$REPO_DIR` (which is the
222
- # installed package directory`<npm root -g>/@polderlabs/bizar/`).
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 foundskipping detailed check"
255
162
  return
256
163
  fi
257
-
258
- local dst="${XDG_CONFIG_HOME:-$HOME/.config}/opencode"
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
- warn "plugin source not found at $REPO_DIR/plugins/bizar/"
167
+ note "all required dependencies satisfied"
309
168
  fi
310
169
  }
311
170
 
312
- # ── Verify opencode.json has the Bizar plugin registered ───────────────────────
313
-
314
- verify_plugin() {
315
- section "Verifying Bizar plugin registration"
316
-
317
- local opencode_config="$CONFIG_DIR/opencode.json"
318
-
319
- if [ ! -f "$opencode_config" ]; then
320
- warn "opencode.json not found at $opencode_config plugin not registered"
321
- return
322
- fi
323
-
324
- # Check if plugin entry exists using node's JSON parser (avoid jq dependency)
325
- local has_plugin
326
- has_plugin=$(node -e "
327
- const cfg = JSON.parse(require('fs').readFileSync('$opencode_config', 'utf8'));
328
- const plugins = cfg.plugin || [];
329
- const found = plugins.some(function(p) {
330
- return Array.isArray(p) && p[0] && p[0].includes('plugins/bizar');
331
- });
332
- console.log(found ? 'yes' : 'no');
333
- " 2>/dev/null || echo "no")
334
-
335
- if [ "$has_plugin" = "yes" ]; then
336
- note "Bizar plugin registered in opencode.json"
337
- return
338
- fi
339
-
340
- action "Adding Bizar plugin entry to opencode.json..."
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
- if $DRY_RUN; then
343
- dim " would patch opencode.json to add plugin entry"
344
- return
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
- # Patch via node script (no jq dependency needed)
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
- # ── Install background service ─────────────────────────────────────────────────
378
-
227
+ # ── Service registration (delegated to Node) ────────────────────────────────
379
228
  install_service() {
380
229
  section "Installing background service"
381
-
382
- if $DRY_RUN; then
383
- dim " would run: node \"$REPO_DIR/scripts/install-service.mjs\""
384
- return
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
- if [ -f "$REPO_DIR/scripts/install-service.mjs" ]; then
388
- local result
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
- # ── Verify .opencode/plugins/bizar exists ──────────────────────────────────────
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:3333"
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 v3.22.0 │"
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
- # ── install_linux: Debian/Ubuntu, Fedora/RHEL, Arch, openSUSE ──────────────────
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
- install_missing_deps
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
- install_missing_deps
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 entry ──────────────────────────────────────────────────────────────────
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 v3.22.0${NC}"
543
- if $UPDATE_MODE; then echo -e " ${DIM}Update mode${NC}"; fi
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
- install_linux
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
- exit 0
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 "$@"