bloby-bot 0.70.12 → 0.71.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.
Files changed (65) hide show
  1. package/bin/cli.js +234 -48
  2. package/dist-bloby/assets/{bloby-DSNB0g4w.js → bloby-es6cZJzs.js} +6 -6
  3. package/dist-bloby/assets/globals-DBqwNiJV.css +2 -0
  4. package/dist-bloby/assets/{globals-B3cTbITX.js → globals-DN3F0CQE.js} +1 -1
  5. package/dist-bloby/assets/{highlighted-body-OFNGDK62-BLforpkr.js → highlighted-body-OFNGDK62-8PiOHw9p.js} +1 -1
  6. package/dist-bloby/assets/mermaid-GHXKKRXX-BJWX8urU.js +1 -0
  7. package/dist-bloby/assets/{onboard-Dn2Ws_G2.js → onboard-BKgy17OU.js} +1 -1
  8. package/dist-bloby/bloby.html +3 -3
  9. package/dist-bloby/onboard.html +3 -3
  10. package/package.json +3 -4
  11. package/scripts/install +156 -41
  12. package/scripts/install.ps1 +146 -29
  13. package/scripts/install.sh +156 -41
  14. package/shared/config.ts +37 -2
  15. package/shared/relay.ts +3 -1
  16. package/supervisor/channels/manager.ts +84 -44
  17. package/supervisor/channels/telegram.ts +57 -16
  18. package/supervisor/channels/types.ts +4 -1
  19. package/supervisor/channels/whatsapp.ts +57 -10
  20. package/supervisor/chat/OnboardWizard.tsx +0 -15
  21. package/supervisor/chat/src/components/Chat/AudioBubble.tsx +1 -1
  22. package/supervisor/chat/src/components/Chat/AuthedImage.tsx +16 -3
  23. package/supervisor/chat/src/components/Chat/BlobyImageCard.tsx +2 -2
  24. package/supervisor/chat/src/components/Chat/ImageLightbox.tsx +25 -8
  25. package/supervisor/chat/src/components/Chat/InputBar.tsx +62 -7
  26. package/supervisor/chat/src/components/Chat/MessageBubble.tsx +37 -18
  27. package/supervisor/chat/src/components/Chat/MessageList.tsx +3 -3
  28. package/supervisor/chat/src/hooks/useChat.ts +52 -0
  29. package/supervisor/chat/src/lib/authedFile.ts +24 -12
  30. package/supervisor/file-saver.ts +92 -19
  31. package/supervisor/harnesses/attachment-policy.ts +111 -0
  32. package/supervisor/harnesses/claude.ts +62 -15
  33. package/supervisor/harnesses/codex.ts +69 -43
  34. package/supervisor/harnesses/pi/index.ts +367 -112
  35. package/supervisor/harnesses/pi/providers/humanize-error.ts +27 -2
  36. package/supervisor/harnesses/pi/providers/retry.ts +31 -0
  37. package/supervisor/harnesses/pi/providers/stream-anthropic.ts +31 -3
  38. package/supervisor/harnesses/pi/providers/stream-google.ts +26 -3
  39. package/supervisor/harnesses/pi/providers/stream-openai-completions.ts +32 -9
  40. package/supervisor/harnesses/pi/providers/types.ts +29 -1
  41. package/supervisor/harnesses/pi/session.ts +143 -3
  42. package/supervisor/harnesses/pi/test-completion.ts +56 -0
  43. package/supervisor/harnesses/pi/tools/bash.ts +198 -22
  44. package/supervisor/harnesses/pi/tools/glob.ts +79 -0
  45. package/supervisor/harnesses/pi/tools/grep.ts +0 -0
  46. package/supervisor/harnesses/pi/tools/registry.ts +18 -6
  47. package/supervisor/harnesses/pi/tools/todo-write.ts +45 -0
  48. package/supervisor/harnesses/pi/tools/web-fetch.ts +129 -0
  49. package/supervisor/index.ts +93 -18
  50. package/supervisor/widget.js +19 -5
  51. package/worker/db.ts +2 -0
  52. package/worker/index.ts +18 -1
  53. package/worker/prompts/bloby-system-prompt-codex.txt +1 -1
  54. package/worker/prompts/bloby-system-prompt-pi.txt +6 -24
  55. package/worker/prompts/bloby-system-prompt.txt +1 -1
  56. package/workspace/client/src/components/Dashboard/DashboardPage.tsx +4 -117
  57. package/workspace/client/src/components/Dashboard/deleteme_placeholders.tsx +194 -0
  58. package/workspace/client/src/components/Layout/Sidebar.tsx +52 -30
  59. package/workspace/client/src/components/deleteme_onboarding/WorkspaceTour.tsx +25 -15
  60. package/workspace/client/src/components/deleteme_onboarding/tour-theme.css +24 -0
  61. package/workspace/skills/mac/SKILL.md +13 -4
  62. package/dist-bloby/assets/globals-DyeW509Y.css +0 -2
  63. package/dist-bloby/assets/mermaid-GHXKKRXX-C1H_fSCU.js +0 -1
  64. package/supervisor/public/headphones_spritesheet.webp +0 -0
  65. package/supervisor/public/spritesheet.webp +0 -0
package/scripts/install CHANGED
@@ -7,6 +7,23 @@ set -e
7
7
  # Downloads Node.js + Bloby into ~/.bloby — no system dependencies needed.
8
8
  # ─────────────────────────────────────────────────────────────────────────────
9
9
 
10
+ # Repair $HOME before deriving any install path. Under `sudo`, cron, CI, or a
11
+ # minimal shell, $HOME can be empty or "/" — without this we'd install into the
12
+ # wrong place (or /). Colors aren't defined yet, so this early error is plain.
13
+ if [ -z "${HOME:-}" ] || [ ! -d "${HOME:-}" ]; then
14
+ _whoami=$(id -un 2>/dev/null || echo "")
15
+ if [ -n "$_whoami" ] && command -v getent >/dev/null 2>&1; then
16
+ HOME=$(getent passwd "$_whoami" 2>/dev/null | cut -d: -f6)
17
+ elif [ -n "$_whoami" ] && command -v dscl >/dev/null 2>&1; then
18
+ HOME=$(dscl . -read "/Users/$_whoami" NFSHomeDirectory 2>/dev/null | awk '{print $2}')
19
+ fi
20
+ export HOME
21
+ fi
22
+ if [ -z "${HOME:-}" ] || [ ! -d "$HOME" ]; then
23
+ printf 'Error: could not determine your home directory ($HOME is unset).\n' >&2
24
+ exit 1
25
+ fi
26
+
10
27
  MIN_NODE_MAJOR=18
11
28
  NODE_VERSION="22.14.0"
12
29
  BLOBY_HOME="$HOME/.bloby"
@@ -14,6 +31,11 @@ TOOLS_DIR="$BLOBY_HOME/tools"
14
31
  NODE_DIR="$TOOLS_DIR/node"
15
32
  BIN_DIR="$BLOBY_HOME/bin"
16
33
  USE_SYSTEM_NODE=false
34
+ LIBC=""
35
+ # Temp paths the cleanup trap removes — kept empty until we own them so an early
36
+ # exit can never `rm -rf` an inherited value (e.g. the system $TMPDIR).
37
+ WORK_DIR=""
38
+ TMPFILE=""
17
39
 
18
40
  # Brand colors: #00ADFE (light) and #0158FB (deep) -- Morphy palette, 24-bit truecolor
19
41
  BLUE='\033[38;2;0;173;254m'
@@ -37,15 +59,47 @@ if [ ! -t 1 ]; then
37
59
  BLUE='' PINK='' YELLOW='' RED='' DIM='' BOLD='' RESET='' G1='' G2='' G3='' G4='' G5='' G6='' G7=''
38
60
  fi
39
61
 
40
- # Cleanup on exit (restore cursor, reset colors, remove temp files)
62
+ # Cleanup on exit (restore cursor, reset colors, remove temp files).
63
+ # Only remove paths we actually created — never an inherited/unset value.
41
64
  cleanup() {
42
65
  printf '\033[?25h' # show cursor
43
66
  printf "${RESET}"
44
- rm -f "$TMPFILE" 2>/dev/null
45
- rm -rf "$TMPDIR" 2>/dev/null
67
+ [ -n "$TMPFILE" ] && rm -f "$TMPFILE" 2>/dev/null
68
+ [ -n "$WORK_DIR" ] && rm -rf "$WORK_DIR" 2>/dev/null
69
+ return 0
46
70
  }
47
71
  trap cleanup EXIT INT TERM
48
72
 
73
+ # ─── Download + integrity helpers ───────────────────────────────────────────
74
+
75
+ # download_file <url> <dest> — curl/wget with HTTPS floor, timeout, and retry.
76
+ download_file() {
77
+ if command -v curl >/dev/null 2>&1; then
78
+ curl -fsSL --proto '=https' --tlsv1.2 --connect-timeout 20 --retry 3 --retry-delay 2 -o "$2" "$1"
79
+ elif command -v wget >/dev/null 2>&1; then
80
+ wget -q --https-only --tries=3 --timeout=20 -O "$2" "$1"
81
+ else
82
+ printf " ${RED}✗${RESET} curl or wget is required to install Bloby\n"
83
+ exit 1
84
+ fi
85
+ }
86
+
87
+ # verify_sha256 <file> <expected-hex> — 0 match, 1 mismatch, 2 no tool available.
88
+ verify_sha256() {
89
+ _actual=""
90
+ if command -v sha256sum >/dev/null 2>&1; then
91
+ _actual=$(sha256sum "$1" 2>/dev/null | awk '{print $1}')
92
+ elif command -v shasum >/dev/null 2>&1; then
93
+ _actual=$(shasum -a 256 "$1" 2>/dev/null | awk '{print $1}')
94
+ else
95
+ return 2
96
+ fi
97
+ # No hash computed (tool failed / file vanished) → "skip" (2), not "mismatch" (1),
98
+ # so we warn rather than aborting with a misleading "tampered download".
99
+ [ -n "$_actual" ] || return 2
100
+ [ "$_actual" = "$2" ]
101
+ }
102
+
49
103
  printf "\n"
50
104
  printf "${G1}${BOLD} █▄ ${RESET}\n"
51
105
  printf "${G2}${BOLD} ▄ ▄ ██ ${RESET}\n"
@@ -83,6 +137,14 @@ detect_platform() {
83
137
  ;;
84
138
  esac
85
139
 
140
+ # Detect musl (Alpine): nodejs.org ships glibc builds only, so a bundled Node
141
+ # would segfault at runtime. Flag it now and require a system Node instead.
142
+ if [ "$PLATFORM" = "linux" ]; then
143
+ if [ -f /etc/alpine-release ] || (ldd --version 2>&1 | grep -qi musl); then
144
+ LIBC="musl"
145
+ fi
146
+ fi
147
+
86
148
  printf " ${DIM}Platform: ${PLATFORM}/${NODEARCH}${RESET}\n"
87
149
  }
88
150
 
@@ -115,35 +177,68 @@ install_node() {
115
177
  fi
116
178
  fi
117
179
 
118
- printf " ${BLUE}↓${RESET} Downloading Node.js v${NODE_VERSION}...\n"
180
+ # nodejs.org ships glibc-linked builds; on musl they segfault at runtime.
181
+ if [ "$LIBC" = "musl" ]; then
182
+ printf " ${RED}✗${RESET} Alpine/musl detected — Bloby's bundled Node.js requires glibc.\n"
183
+ printf " ${DIM}Install Node.js >= ${MIN_NODE_MAJOR} (e.g. ${BOLD}apk add nodejs npm${RESET}${DIM}) and re-run this installer.${RESET}\n"
184
+ exit 1
185
+ fi
119
186
 
120
- NODE_URL="https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-${PLATFORM}-${NODEARCH}.tar.xz"
121
- TMPFILE=$(mktemp /tmp/node-XXXXXX.tar.xz)
187
+ printf " ${BLUE}↓${RESET} Downloading Node.js v${NODE_VERSION}...\n"
122
188
 
123
- # Download
124
- if command -v curl >/dev/null 2>&1; then
125
- curl -fsSL -o "$TMPFILE" "$NODE_URL"
126
- elif command -v wget >/dev/null 2>&1; then
127
- wget -qO "$TMPFILE" "$NODE_URL"
189
+ NODE_FILE="node-v${NODE_VERSION}-${PLATFORM}-${NODEARCH}.tar.xz"
190
+ NODE_URL="https://nodejs.org/dist/v${NODE_VERSION}/${NODE_FILE}"
191
+ # Trailing X's, NO suffix: BSD mktemp (macOS) only substitutes trailing X's,
192
+ # so a ".tar.xz" suffix would create a literal, predictable, non-unique file.
193
+ TMPFILE=$(mktemp "${TMPDIR:-/tmp}/bloby-node-XXXXXX")
194
+ download_file "$NODE_URL" "$TMPFILE"
195
+
196
+ # Integrity: verify against nodejs.org SHASUMS256.txt before extracting. A
197
+ # mismatch is fatal; an unreachable sums file or missing hash tool degrades to
198
+ # a warning so installs still proceed (the download itself is TLS-protected).
199
+ printf " ${DIM}Verifying download...${RESET}\n"
200
+ EXPECTED_SHA=""
201
+ SUMS=$(curl -fsSL --proto '=https' --tlsv1.2 --connect-timeout 20 "https://nodejs.org/dist/v${NODE_VERSION}/SHASUMS256.txt" 2>/dev/null \
202
+ || wget -q --https-only --timeout=20 -O- "https://nodejs.org/dist/v${NODE_VERSION}/SHASUMS256.txt" 2>/dev/null \
203
+ || echo "")
204
+ if [ -n "$SUMS" ]; then
205
+ EXPECTED_SHA=$(printf '%s\n' "$SUMS" | awk -v f="$NODE_FILE" '$2==f {print $1; exit}')
206
+ fi
207
+ if [ -n "$EXPECTED_SHA" ]; then
208
+ vrc=0
209
+ verify_sha256 "$TMPFILE" "$EXPECTED_SHA" || vrc=$?
210
+ if [ "$vrc" = "0" ]; then
211
+ printf " ${BLUE}✔${RESET} Checksum verified\n"
212
+ elif [ "$vrc" = "2" ]; then
213
+ printf " ${YELLOW}!${RESET} Could not compute checksum — skipping verification\n"
214
+ else
215
+ printf " ${RED}✗${RESET} Node.js checksum mismatch — aborting (corrupt or tampered download)\n"
216
+ exit 1
217
+ fi
128
218
  else
129
- printf " ${RED}✗${RESET} curl or wget required\n"
130
- exit 1
219
+ printf " ${YELLOW}!${RESET} Could not fetch checksums — skipping verification\n"
131
220
  fi
132
221
 
133
- # Extract
222
+ # Extract into a staging dir, verify it runs, then atomically swap in. An
223
+ # interrupt mid-extract no longer wipes the existing (working) node.
134
224
  mkdir -p "$TOOLS_DIR"
135
- rm -rf "$NODE_DIR"
136
- mkdir -p "$NODE_DIR"
137
-
138
- tar xf "$TMPFILE" -C "$NODE_DIR" --strip-components=1
139
- rm -f "$TMPFILE"
140
-
141
- # Verify
142
- if [ ! -x "$NODE_DIR/bin/node" ]; then
143
- printf " ${RED}✗${RESET} Node.js download failed\n"
225
+ NODE_NEW="$NODE_DIR.new"
226
+ rm -rf "$NODE_NEW"
227
+ mkdir -p "$NODE_NEW"
228
+ tar xf "$TMPFILE" -C "$NODE_NEW" --strip-components=1
229
+ rm -f "$TMPFILE"; TMPFILE=""
230
+
231
+ if ! "$NODE_NEW/bin/node" -v >/dev/null 2>&1; then
232
+ printf " ${RED}✗${RESET} Node.js download failed (extracted binary does not run)\n"
233
+ rm -rf "$NODE_NEW"
144
234
  exit 1
145
235
  fi
146
236
 
237
+ rm -rf "$NODE_DIR.old"
238
+ [ -e "$NODE_DIR" ] && mv "$NODE_DIR" "$NODE_DIR.old"
239
+ mv "$NODE_NEW" "$NODE_DIR"
240
+ rm -rf "$NODE_DIR.old"
241
+
147
242
  printf " ${BLUE}✔${RESET} Node.js v${NODE_VERSION} installed\n"
148
243
  }
149
244
 
@@ -174,19 +269,15 @@ install_bloby() {
174
269
  exit 1
175
270
  fi
176
271
 
177
- # Download and extract tarball
178
- TMPDIR=$(mktemp -d)
179
- if command -v curl >/dev/null 2>&1; then
180
- curl -fsSL -o "$TMPDIR/bloby.tgz" "$TARBALL_URL"
181
- elif command -v wget >/dev/null 2>&1; then
182
- wget -qO "$TMPDIR/bloby.tgz" "$TARBALL_URL"
183
- fi
272
+ # Download and extract tarball (download_file guards on missing curl/wget)
273
+ WORK_DIR=$(mktemp -d)
274
+ download_file "$TARBALL_URL" "$WORK_DIR/bloby.tgz"
184
275
 
185
- tar xzf "$TMPDIR/bloby.tgz" -C "$TMPDIR"
186
- EXTRACTED="$TMPDIR/package"
276
+ tar xzf "$WORK_DIR/bloby.tgz" -C "$WORK_DIR"
277
+ EXTRACTED="$WORK_DIR/package"
187
278
 
188
279
  if [ ! -d "$EXTRACTED" ]; then
189
- rm -rf "$TMPDIR"
280
+ rm -rf "$WORK_DIR"; WORK_DIR=""
190
281
  printf " ${RED}✗${RESET} Installation failed\n"
191
282
  exit 1
192
283
  fi
@@ -219,7 +310,7 @@ install_bloby() {
219
310
  fi
220
311
  fi
221
312
 
222
- rm -rf "$TMPDIR"
313
+ rm -rf "$WORK_DIR"; WORK_DIR=""
223
314
 
224
315
  # Install dependencies inside ~/.bloby/
225
316
  # claude-agent-sdk 0.3.x moved @anthropic-ai/sdk + @modelcontextprotocol/sdk to
@@ -245,6 +336,15 @@ install_bloby() {
245
336
  WS_INSTALL_LOG=$(mktemp)
246
337
  if ! (cd "$BLOBY_HOME/workspace" && "$NPM" install --omit=dev > "$WS_INSTALL_LOG" 2>&1); then
247
338
  printf " ${RED}✗${RESET} Workspace dependency install failed:\n"
339
+ # Native modules (better-sqlite3 etc.) need a compiler toolchain — detect
340
+ # the common "missing build tools" failure and point at the exact fix.
341
+ if grep -qiE 'make: .*command not found|gyp ERR! find Python|no developer tools|xcode-select|command not found: make|g\+\+: not found' "$WS_INSTALL_LOG"; then
342
+ if [ "$PLATFORM" = "darwin" ]; then
343
+ printf " ${YELLOW}!${RESET} Native build tools missing. Run ${BOLD}xcode-select --install${RESET} then re-run this installer.\n"
344
+ else
345
+ printf " ${YELLOW}!${RESET} Native build tools missing. On Debian/Ubuntu: ${BOLD}sudo apt-get install -y build-essential python3${RESET}, then re-run.\n"
346
+ fi
347
+ fi
248
348
  cat "$WS_INSTALL_LOG"
249
349
  rm -f "$WS_INSTALL_LOG"
250
350
  exit 1
@@ -348,19 +448,34 @@ install_bloby
348
448
  create_wrapper
349
449
  setup_path
350
450
 
451
+ # Smoke-test: the wrapper + node + cli must actually run, not just exist on disk.
452
+ if ! "$BIN_DIR/bloby" --version >/dev/null 2>&1; then
453
+ printf "\n ${RED}✗${RESET} Bloby installed but failed to run (\`bloby --version\`).\n"
454
+ printf " ${DIM}Open a NEW terminal and run ${BOLD}bloby --version${RESET}${DIM}; if it still fails, re-run this installer.${RESET}\n\n"
455
+ exit 1
456
+ fi
457
+
351
458
  printf "\n"
352
- printf " ${PINK}${BOLD}✔ Bloby is ready!${RESET}\n"
459
+ printf " ${PINK}${BOLD}✔ Bloby is installed!${RESET}\n"
353
460
  printf "\n"
354
- printf " ${DIM}─────────────────────────────${RESET}\n"
355
- printf " ${BOLD}Get started:${RESET}\n"
461
+ printf " ${BLUE}${BOLD}╭───────────────────────────────────────────────────────╮${RESET}\n"
462
+ printf " ${BLUE}${BOLD}│${RESET} ${RESET}${BLUE}${BOLD}│${RESET}\n"
463
+ printf " ${BLUE}${BOLD}│${RESET}${BOLD} NEXT STEP - type this, then press Enter:${RESET} ${BLUE}${BOLD}│${RESET}\n"
464
+ printf " ${BLUE}${BOLD}│${RESET} ${RESET}${BLUE}${BOLD}│${RESET}\n"
465
+ printf " ${BLUE}${BOLD}│${RESET}${BLUE}${BOLD} > bloby init${RESET} ${BLUE}${BOLD}│${RESET}\n"
466
+ printf " ${BLUE}${BOLD}│${RESET} ${RESET}${BLUE}${BOLD}│${RESET}\n"
467
+ printf " ${BLUE}${BOLD}│${RESET}${DIM} ─────────────────────────────────────────────────${RESET} ${BLUE}${BOLD}│${RESET}\n"
468
+ printf " ${BLUE}${BOLD}│${RESET} ${RESET}${BLUE}${BOLD}│${RESET}\n"
469
+ printf " ${BLUE}${BOLD}│${RESET}${YELLOW}${BOLD} Not working? (\"command not found\")${RESET} ${BLUE}${BOLD}│${RESET}\n"
470
+ printf " ${BLUE}${BOLD}│${RESET} Just open a NEW terminal window and run${RESET} ${BLUE}${BOLD}│${RESET}\n"
471
+ printf " ${BLUE}${BOLD}│${RESET} bloby init again.${RESET} ${BLUE}${BOLD}│${RESET}\n"
472
+ printf " ${BLUE}${BOLD}│${RESET} ${RESET}${BLUE}${BOLD}│${RESET}\n"
473
+ printf " ${BLUE}${BOLD}╰───────────────────────────────────────────────────────╯${RESET}\n"
356
474
  printf "\n"
357
- printf " ${BLUE}bloby init${RESET} Set up your bot\n"
475
+ printf " ${DIM}Other commands:${RESET}\n"
358
476
  printf " ${BLUE}bloby start${RESET} Start your bot\n"
359
477
  printf " ${BLUE}bloby status${RESET} Check if it's running\n"
360
478
  printf " ${BLUE}bloby help${RESET} All commands\n"
361
479
  printf "\n"
362
- printf " ${PINK}>${RESET} Run ${BLUE}bloby init${RESET} to begin.\n"
363
- printf " ${DIM}(Open a new terminal if 'bloby' isn't found yet)${RESET}\n"
364
- printf "\n"
365
480
  printf " ${DIM}https://bloby.bot${RESET}\n"
366
481
  printf "\n"
@@ -6,6 +6,30 @@
6
6
 
7
7
  $ErrorActionPreference = "Stop"
8
8
 
9
+ # TLS 1.2 floor for Windows PowerShell 5.1 (which can default to TLS 1.0/1.1 and
10
+ # fail against nodejs.org/npm). OR-in so we never DROP TLS 1.3 on PowerShell 7+.
11
+ try {
12
+ [Net.ServicePointManager]::SecurityProtocol = `
13
+ [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
14
+ } catch {}
15
+
16
+ # Get-Url <url> <outFile> — download with a timeout and up to 3 attempts so a
17
+ # single transient blip doesn't abort the whole install. PS 5.1-compatible
18
+ # (no -MaximumRetryCount, which is PowerShell 6+ only).
19
+ function Get-Url($url, $outFile) {
20
+ $attempt = 0
21
+ while ($true) {
22
+ $attempt++
23
+ try {
24
+ Invoke-WebRequest -Uri $url -OutFile $outFile -UseBasicParsing -TimeoutSec 60
25
+ return
26
+ } catch {
27
+ if ($attempt -ge 3) { throw }
28
+ Start-Sleep -Seconds 2
29
+ }
30
+ }
31
+ }
32
+
9
33
  $MIN_NODE_MAJOR = 18
10
34
  $NODE_VERSION = "22.14.0"
11
35
  $BLOBY_HOME = Join-Path $env:USERPROFILE ".bloby"
@@ -29,6 +53,8 @@ $G5 = "`e[38;2;1;116;252m"
29
53
  $G6 = "`e[38;2;1;102;251m"
30
54
  $G7 = "`e[38;2;1;88;251m"
31
55
  $BOLD = "`e[1m"
56
+ $DIM = "`e[2m"
57
+ $YELLOW = "`e[33m"
32
58
  $RSET = "`e[0m"
33
59
 
34
60
  # Use ANSI sequences for consistent rendering; fallback to plain if no VT support
@@ -123,25 +149,64 @@ function Install-Node {
123
149
 
124
150
  Write-Down "Downloading Node.js v${NODE_VERSION}..."
125
151
 
126
- $nodeUrl = "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-win-${NODEARCH}.zip"
152
+ $nodeFile = "node-v${NODE_VERSION}-win-${NODEARCH}.zip"
153
+ $nodeUrl = "https://nodejs.org/dist/v${NODE_VERSION}/$nodeFile"
127
154
  $tmpFile = Join-Path ([System.IO.Path]::GetTempPath()) "node-bloby.zip"
128
155
 
129
- Invoke-WebRequest -Uri $nodeUrl -OutFile $tmpFile -UseBasicParsing
156
+ Get-Url $nodeUrl $tmpFile
130
157
 
131
- # Extract
132
- New-Item -ItemType Directory -Path $TOOLS_DIR -Force | Out-Null
133
- if (Test-Path $NODE_DIR) { Remove-Item $NODE_DIR -Recurse -Force }
158
+ # Integrity: verify against nodejs.org SHASUMS256.txt before extracting. A
159
+ # mismatch is fatal; an unreachable sums file degrades to a warning.
160
+ try {
161
+ $sums = (Invoke-WebRequest -Uri "https://nodejs.org/dist/v${NODE_VERSION}/SHASUMS256.txt" -UseBasicParsing -TimeoutSec 30).Content
162
+ $line = $sums -split "`n" | Where-Object { $_ -match ([regex]::Escape($nodeFile) + '\s*$') } | Select-Object -First 1
163
+ if ($line) {
164
+ $expectedHash = ($line -split '\s+')[0]
165
+ $actualHash = (Get-FileHash -Path $tmpFile -Algorithm SHA256).Hash
166
+ if ($actualHash -ieq $expectedHash) {
167
+ Write-Check "Checksum verified"
168
+ } else {
169
+ Write-Host " ✗ Node.js checksum mismatch — aborting (corrupt or tampered download)" -ForegroundColor Red
170
+ Remove-Item $tmpFile -Force -ErrorAction SilentlyContinue
171
+ exit 1
172
+ }
173
+ } else {
174
+ Write-Host " ! Could not find checksum entry — skipping verification" -ForegroundColor Yellow
175
+ }
176
+ } catch {
177
+ Write-Host " ! Could not fetch checksums — skipping verification" -ForegroundColor Yellow
178
+ }
134
179
 
180
+ # Extract into a staging dir, verify it runs, then swap. Move-Item cannot
181
+ # overwrite a populated directory, so the existing node is moved aside and
182
+ # removed only AFTER the new tree is verified — an interrupt never strands us.
183
+ New-Item -ItemType Directory -Path $TOOLS_DIR -Force | Out-Null
135
184
  $tmpExtract = Join-Path ([System.IO.Path]::GetTempPath()) "node-bloby-extract"
136
185
  if (Test-Path $tmpExtract) { Remove-Item $tmpExtract -Recurse -Force }
137
186
 
138
187
  Expand-Archive -Path $tmpFile -DestinationPath $tmpExtract -Force
139
188
  $extracted = Get-ChildItem $tmpExtract | Select-Object -First 1
140
- Move-Item -Path $extracted.FullName -Destination $NODE_DIR -Force
189
+ $nodeNew = "$NODE_DIR.new"
190
+ if (Test-Path $nodeNew) { Remove-Item $nodeNew -Recurse -Force }
191
+ Move-Item -Path $extracted.FullName -Destination $nodeNew -Force
141
192
 
142
193
  Remove-Item $tmpFile -Force -ErrorAction SilentlyContinue
143
194
  Remove-Item $tmpExtract -Recurse -Force -ErrorAction SilentlyContinue
144
195
 
196
+ $stagedNode = Join-Path $nodeNew "node.exe"
197
+ $stagedOk = $false
198
+ try { $stagedOk = [bool](& $stagedNode -v 2>$null) } catch {}
199
+ if (-not $stagedOk) {
200
+ Write-Host " ✗ Node.js download failed (extracted binary does not run)" -ForegroundColor Red
201
+ Remove-Item $nodeNew -Recurse -Force -ErrorAction SilentlyContinue
202
+ exit 1
203
+ }
204
+
205
+ if (Test-Path "$NODE_DIR.old") { Remove-Item "$NODE_DIR.old" -Recurse -Force -ErrorAction SilentlyContinue }
206
+ if (Test-Path $NODE_DIR) { Move-Item $NODE_DIR "$NODE_DIR.old" -Force }
207
+ Move-Item $nodeNew $NODE_DIR -Force
208
+ if (Test-Path "$NODE_DIR.old") { Remove-Item "$NODE_DIR.old" -Recurse -Force -ErrorAction SilentlyContinue }
209
+
145
210
  # Verify
146
211
  $nodeBin = Join-Path $NODE_DIR "node.exe"
147
212
  if (-not (Test-Path $nodeBin)) {
@@ -172,9 +237,13 @@ function Install-Bloby {
172
237
 
173
238
  Write-Down "Installing bloby..."
174
239
 
175
- $tarballUrl = (& $NPM view bloby-bot dist.tarball 2>$null).Trim()
240
+ # Capture first, THEN .Trim() calling .Trim() on a null (offline npm) throws
241
+ # under $ErrorActionPreference='Stop' and skips the friendly message below.
242
+ $tarballUrl = ""
243
+ try { $tarballUrl = (& $NPM view bloby-bot dist.tarball 2>$null) } catch {}
244
+ if ($tarballUrl) { $tarballUrl = $tarballUrl.Trim() }
176
245
  if (-not $tarballUrl) {
177
- Write-Host " ✗ Failed to fetch package info from npm" -ForegroundColor Red
246
+ Write-Host " ✗ Failed to fetch package info from npm (are you online?)" -ForegroundColor Red
178
247
  exit 1
179
248
  }
180
249
 
@@ -184,7 +253,7 @@ function Install-Bloby {
184
253
 
185
254
  try {
186
255
  $tarball = Join-Path $tmpDir "bloby.tgz"
187
- Invoke-WebRequest -Uri $tarballUrl -OutFile $tarball -UseBasicParsing
256
+ Get-Url $tarballUrl $tarball
188
257
 
189
258
  tar xzf $tarball -C $tmpDir
190
259
 
@@ -253,21 +322,39 @@ function Install-Bloby {
253
322
  if (-not ((Test-Path $npmrc) -and (Select-String -Path $npmrc -Pattern '^legacy-peer-deps' -Quiet))) {
254
323
  Add-Content -Path $npmrc -Value 'legacy-peer-deps=true'
255
324
  }
325
+ # Toggle EAP to Continue around the native npm call so a stderr line doesn't
326
+ # raise a NativeCommandError; then gate on the real exit code. A broken dep
327
+ # tree is fatal here (matching install.sh) instead of being silently ignored.
256
328
  Push-Location $BLOBY_HOME
257
- try {
258
- & $NPM install --omit=dev 2>$null
259
- } catch {}
329
+ $eap = $ErrorActionPreference; $ErrorActionPreference = "Continue"
330
+ $npmOut = & $NPM install --omit=dev 2>&1
331
+ $npmCode = $LASTEXITCODE
332
+ $ErrorActionPreference = $eap
260
333
  Pop-Location
334
+ if ($npmCode -ne 0) {
335
+ Write-Host " ✗ Dependency install failed:" -ForegroundColor Red
336
+ $npmOut | ForEach-Object { Write-Host " $_" }
337
+ exit 1
338
+ }
261
339
 
262
340
  # Install workspace dependencies (rebuilds native modules for this platform)
263
341
  $wsDir = Join-Path $BLOBY_HOME "workspace"
264
342
  if (Test-Path (Join-Path $wsDir "package.json")) {
265
343
  Write-Down "Installing workspace dependencies..."
266
344
  Push-Location $wsDir
267
- try {
268
- & $NPM install --omit=dev 2>$null
269
- } catch {}
345
+ $eap = $ErrorActionPreference; $ErrorActionPreference = "Continue"
346
+ $wsOut = & $NPM install --omit=dev 2>&1
347
+ $wsCode = $LASTEXITCODE
348
+ $ErrorActionPreference = $eap
270
349
  Pop-Location
350
+ if ($wsCode -ne 0) {
351
+ Write-Host " ✗ Workspace dependency install failed:" -ForegroundColor Red
352
+ if ("$wsOut" -match 'Visual Studio|gyp ERR|MSBuild|node-gyp|Python') {
353
+ Write-Host " ! Native build tools missing. Install the Visual Studio Build Tools (Desktop C++ workload) + Python 3, then re-run. See https://github.com/nodejs/node-gyp#on-windows" -ForegroundColor Yellow
354
+ }
355
+ $wsOut | ForEach-Object { Write-Host " $_" }
356
+ exit 1
357
+ }
271
358
  }
272
359
 
273
360
  # Verify
@@ -334,33 +421,63 @@ Install-Bloby
334
421
  Create-Wrapper
335
422
  Setup-Path
336
423
 
424
+ # Smoke-test: the wrapper + node + cli must actually run, not just exist on disk.
425
+ $blobyCmd = Join-Path $BIN_DIR "bloby.cmd"
426
+ $smokeOk = $false
427
+ try { $smokeOk = [bool](& $blobyCmd --version 2>$null) } catch {}
428
+ if (-not $smokeOk) {
429
+ Write-Host ""
430
+ Write-Host " ✗ Bloby installed but failed to run (bloby --version)." -ForegroundColor Red
431
+ Write-Host " Open a NEW terminal and run 'bloby --version'; if it still fails, re-run this installer." -ForegroundColor DarkGray
432
+ exit 1
433
+ }
434
+
337
435
  Write-Host ""
338
436
  if ($vtSupported) {
339
- Write-Host " ${PINK}${BOLD}✔ Bloby is ready!${RSET}"
437
+ Write-Host " ${PINK}${BOLD}✔ Bloby is installed!${RSET}"
340
438
  } else {
341
- Write-Host " ✔ Bloby is ready!" -ForegroundColor Magenta
439
+ Write-Host " ✔ Bloby is installed!" -ForegroundColor Magenta
342
440
  }
343
441
  Write-Host ""
344
- Write-Host " -----------------------------" -ForegroundColor DarkGray
345
- Write-Host " Get started:"
442
+ if ($vtSupported) {
443
+ Write-Host " ${BLUE}${BOLD}╭───────────────────────────────────────────────────────╮${RSET}"
444
+ Write-Host " ${BLUE}${BOLD}│${RSET} ${RSET}${BLUE}${BOLD}│${RSET}"
445
+ Write-Host " ${BLUE}${BOLD}│${RSET}${BOLD} NEXT STEP - type this, then press Enter:${RSET} ${BLUE}${BOLD}│${RSET}"
446
+ Write-Host " ${BLUE}${BOLD}│${RSET} ${RSET}${BLUE}${BOLD}│${RSET}"
447
+ Write-Host " ${BLUE}${BOLD}│${RSET}${BLUE}${BOLD} > bloby init${RSET} ${BLUE}${BOLD}│${RSET}"
448
+ Write-Host " ${BLUE}${BOLD}│${RSET} ${RSET}${BLUE}${BOLD}│${RSET}"
449
+ Write-Host " ${BLUE}${BOLD}│${RSET}${DIM} ─────────────────────────────────────────────────${RSET} ${BLUE}${BOLD}│${RSET}"
450
+ Write-Host " ${BLUE}${BOLD}│${RSET} ${RSET}${BLUE}${BOLD}│${RSET}"
451
+ Write-Host " ${BLUE}${BOLD}│${RSET}${YELLOW}${BOLD} Not working? (`"command not found`")${RSET} ${BLUE}${BOLD}│${RSET}"
452
+ Write-Host " ${BLUE}${BOLD}│${RSET} Just open a NEW terminal window and run${RSET} ${BLUE}${BOLD}│${RSET}"
453
+ Write-Host " ${BLUE}${BOLD}│${RSET} bloby init again.${RSET} ${BLUE}${BOLD}│${RSET}"
454
+ Write-Host " ${BLUE}${BOLD}│${RSET} ${RSET}${BLUE}${BOLD}│${RSET}"
455
+ Write-Host " ${BLUE}${BOLD}╰───────────────────────────────────────────────────────╯${RSET}"
456
+ } else {
457
+ Write-Host " +-------------------------------------------------------+" -ForegroundColor Cyan
458
+ Write-Host " | |" -ForegroundColor Cyan
459
+ Write-Host " | NEXT STEP - type this, then press Enter: |" -ForegroundColor Cyan
460
+ Write-Host " | |" -ForegroundColor Cyan
461
+ Write-Host " | > bloby init |" -ForegroundColor Cyan
462
+ Write-Host " | |" -ForegroundColor Cyan
463
+ Write-Host " | Not working? (`"command not found`") |" -ForegroundColor Cyan
464
+ Write-Host " | Just open a NEW terminal window and run |" -ForegroundColor Cyan
465
+ Write-Host " | bloby init again. |" -ForegroundColor Cyan
466
+ Write-Host " | |" -ForegroundColor Cyan
467
+ Write-Host " +-------------------------------------------------------+" -ForegroundColor Cyan
468
+ }
346
469
  Write-Host ""
347
470
  if ($vtSupported) {
348
- Write-Host " ${BLUE}bloby init${RSET} Set up your bot"
471
+ Write-Host " ${DIM}Other commands:${RSET}"
349
472
  Write-Host " ${BLUE}bloby start${RSET} Start your bot"
350
473
  Write-Host " ${BLUE}bloby status${RSET} Check if it's running"
351
- Write-Host ""
352
- Write-Host " ${PINK}>${RSET} Run ${BLUE}bloby init${RSET} to begin."
474
+ Write-Host " ${BLUE}bloby help${RSET} All commands"
353
475
  } else {
354
- Write-Host " bloby init " -ForegroundColor Cyan -NoNewline; Write-Host "Set up your bot"
476
+ Write-Host " Other commands:" -ForegroundColor DarkGray
355
477
  Write-Host " bloby start " -ForegroundColor Cyan -NoNewline; Write-Host "Start your bot"
356
478
  Write-Host " bloby status " -ForegroundColor Cyan -NoNewline; Write-Host "Check if it's running"
357
- Write-Host ""
358
- Write-Host " > " -ForegroundColor Magenta -NoNewline
359
- Write-Host "Run " -NoNewline
360
- Write-Host "bloby init" -ForegroundColor Cyan -NoNewline
361
- Write-Host " to begin."
479
+ Write-Host " bloby help " -ForegroundColor Cyan -NoNewline; Write-Host "All commands"
362
480
  }
363
- Write-Host " (Open a new terminal if 'bloby' isn't found yet)" -ForegroundColor DarkGray
364
481
  Write-Host ""
365
482
  Write-Host " https://bloby.bot" -ForegroundColor DarkGray
366
483
  Write-Host ""