bloby-bot 0.70.13 → 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.
- package/bin/cli.js +223 -45
- package/dist-bloby/assets/{bloby-CU9KhQdP.js → bloby-es6cZJzs.js} +6 -6
- package/dist-bloby/assets/globals-DBqwNiJV.css +2 -0
- package/dist-bloby/assets/{highlighted-body-OFNGDK62-D0Tm_wgU.js → highlighted-body-OFNGDK62-8PiOHw9p.js} +1 -1
- package/dist-bloby/assets/mermaid-GHXKKRXX-BJWX8urU.js +1 -0
- package/dist-bloby/assets/{onboard-GfjHF9nm.js → onboard-BKgy17OU.js} +1 -1
- package/dist-bloby/bloby.html +3 -3
- package/dist-bloby/onboard.html +3 -3
- package/package.json +2 -3
- package/scripts/install +141 -34
- package/scripts/install.ps1 +111 -15
- package/scripts/install.sh +141 -34
- package/shared/config.ts +37 -2
- package/supervisor/channels/manager.ts +68 -33
- package/supervisor/channels/telegram.ts +57 -16
- package/supervisor/channels/types.ts +4 -1
- package/supervisor/channels/whatsapp.ts +57 -10
- package/supervisor/chat/src/components/Chat/AudioBubble.tsx +1 -1
- package/supervisor/chat/src/components/Chat/AuthedImage.tsx +16 -3
- package/supervisor/chat/src/components/Chat/BlobyImageCard.tsx +2 -2
- package/supervisor/chat/src/components/Chat/ImageLightbox.tsx +25 -8
- package/supervisor/chat/src/components/Chat/InputBar.tsx +62 -7
- package/supervisor/chat/src/components/Chat/MessageBubble.tsx +37 -18
- package/supervisor/chat/src/components/Chat/MessageList.tsx +3 -3
- package/supervisor/chat/src/hooks/useChat.ts +52 -0
- package/supervisor/chat/src/lib/authedFile.ts +24 -12
- package/supervisor/file-saver.ts +92 -19
- package/supervisor/harnesses/attachment-policy.ts +111 -0
- package/supervisor/harnesses/claude.ts +62 -15
- package/supervisor/harnesses/codex.ts +69 -43
- package/supervisor/harnesses/pi/index.ts +84 -49
- package/supervisor/harnesses/pi/providers/humanize-error.ts +25 -0
- package/supervisor/harnesses/pi/providers/stream-anthropic.ts +8 -0
- package/supervisor/harnesses/pi/providers/stream-google.ts +5 -0
- package/supervisor/harnesses/pi/providers/stream-openai-completions.ts +15 -6
- package/supervisor/harnesses/pi/providers/types.ts +18 -1
- package/supervisor/harnesses/pi/session.ts +28 -1
- package/supervisor/index.ts +57 -16
- package/supervisor/widget.js +19 -5
- package/worker/db.ts +2 -0
- package/dist-bloby/assets/globals-DlPtwiZL.css +0 -2
- package/dist-bloby/assets/mermaid-GHXKKRXX-B95J3s3s.js +0 -1
- package/supervisor/public/headphones_spritesheet.webp +0 -0
- package/supervisor/public/spritesheet.webp +0 -0
- /package/dist-bloby/assets/{globals-mGpojCOe.js → globals-DN3F0CQE.js} +0 -0
package/scripts/install.ps1
CHANGED
|
@@ -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"
|
|
@@ -125,25 +149,64 @@ function Install-Node {
|
|
|
125
149
|
|
|
126
150
|
Write-Down "Downloading Node.js v${NODE_VERSION}..."
|
|
127
151
|
|
|
128
|
-
$
|
|
152
|
+
$nodeFile = "node-v${NODE_VERSION}-win-${NODEARCH}.zip"
|
|
153
|
+
$nodeUrl = "https://nodejs.org/dist/v${NODE_VERSION}/$nodeFile"
|
|
129
154
|
$tmpFile = Join-Path ([System.IO.Path]::GetTempPath()) "node-bloby.zip"
|
|
130
155
|
|
|
131
|
-
|
|
156
|
+
Get-Url $nodeUrl $tmpFile
|
|
132
157
|
|
|
133
|
-
#
|
|
134
|
-
|
|
135
|
-
|
|
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
|
+
}
|
|
136
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
|
|
137
184
|
$tmpExtract = Join-Path ([System.IO.Path]::GetTempPath()) "node-bloby-extract"
|
|
138
185
|
if (Test-Path $tmpExtract) { Remove-Item $tmpExtract -Recurse -Force }
|
|
139
186
|
|
|
140
187
|
Expand-Archive -Path $tmpFile -DestinationPath $tmpExtract -Force
|
|
141
188
|
$extracted = Get-ChildItem $tmpExtract | Select-Object -First 1
|
|
142
|
-
|
|
189
|
+
$nodeNew = "$NODE_DIR.new"
|
|
190
|
+
if (Test-Path $nodeNew) { Remove-Item $nodeNew -Recurse -Force }
|
|
191
|
+
Move-Item -Path $extracted.FullName -Destination $nodeNew -Force
|
|
143
192
|
|
|
144
193
|
Remove-Item $tmpFile -Force -ErrorAction SilentlyContinue
|
|
145
194
|
Remove-Item $tmpExtract -Recurse -Force -ErrorAction SilentlyContinue
|
|
146
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
|
+
|
|
147
210
|
# Verify
|
|
148
211
|
$nodeBin = Join-Path $NODE_DIR "node.exe"
|
|
149
212
|
if (-not (Test-Path $nodeBin)) {
|
|
@@ -174,9 +237,13 @@ function Install-Bloby {
|
|
|
174
237
|
|
|
175
238
|
Write-Down "Installing bloby..."
|
|
176
239
|
|
|
177
|
-
|
|
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() }
|
|
178
245
|
if (-not $tarballUrl) {
|
|
179
|
-
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
|
|
180
247
|
exit 1
|
|
181
248
|
}
|
|
182
249
|
|
|
@@ -186,7 +253,7 @@ function Install-Bloby {
|
|
|
186
253
|
|
|
187
254
|
try {
|
|
188
255
|
$tarball = Join-Path $tmpDir "bloby.tgz"
|
|
189
|
-
|
|
256
|
+
Get-Url $tarballUrl $tarball
|
|
190
257
|
|
|
191
258
|
tar xzf $tarball -C $tmpDir
|
|
192
259
|
|
|
@@ -255,21 +322,39 @@ function Install-Bloby {
|
|
|
255
322
|
if (-not ((Test-Path $npmrc) -and (Select-String -Path $npmrc -Pattern '^legacy-peer-deps' -Quiet))) {
|
|
256
323
|
Add-Content -Path $npmrc -Value 'legacy-peer-deps=true'
|
|
257
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.
|
|
258
328
|
Push-Location $BLOBY_HOME
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
329
|
+
$eap = $ErrorActionPreference; $ErrorActionPreference = "Continue"
|
|
330
|
+
$npmOut = & $NPM install --omit=dev 2>&1
|
|
331
|
+
$npmCode = $LASTEXITCODE
|
|
332
|
+
$ErrorActionPreference = $eap
|
|
262
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
|
+
}
|
|
263
339
|
|
|
264
340
|
# Install workspace dependencies (rebuilds native modules for this platform)
|
|
265
341
|
$wsDir = Join-Path $BLOBY_HOME "workspace"
|
|
266
342
|
if (Test-Path (Join-Path $wsDir "package.json")) {
|
|
267
343
|
Write-Down "Installing workspace dependencies..."
|
|
268
344
|
Push-Location $wsDir
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
345
|
+
$eap = $ErrorActionPreference; $ErrorActionPreference = "Continue"
|
|
346
|
+
$wsOut = & $NPM install --omit=dev 2>&1
|
|
347
|
+
$wsCode = $LASTEXITCODE
|
|
348
|
+
$ErrorActionPreference = $eap
|
|
272
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
|
+
}
|
|
273
358
|
}
|
|
274
359
|
|
|
275
360
|
# Verify
|
|
@@ -336,6 +421,17 @@ Install-Bloby
|
|
|
336
421
|
Create-Wrapper
|
|
337
422
|
Setup-Path
|
|
338
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
|
+
|
|
339
435
|
Write-Host ""
|
|
340
436
|
if ($vtSupported) {
|
|
341
437
|
Write-Host " ${PINK}${BOLD}✔ Bloby is installed!${RSET}"
|
package/scripts/install.sh
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 "$
|
|
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
|
-
|
|
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
|
-
|
|
121
|
-
TMPFILE=$(mktemp /tmp/node-XXXXXX.tar.xz)
|
|
187
|
+
printf " ${BLUE}↓${RESET} Downloading Node.js v${NODE_VERSION}...\n"
|
|
122
188
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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 " ${
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
tar xf "$TMPFILE" -C "$
|
|
139
|
-
rm -f "$TMPFILE"
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
179
|
-
|
|
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 "$
|
|
186
|
-
EXTRACTED="$
|
|
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 "$
|
|
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 "$
|
|
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,6 +448,13 @@ 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
459
|
printf " ${PINK}${BOLD}✔ Bloby is installed!${RESET}\n"
|
|
353
460
|
printf "\n"
|
package/shared/config.ts
CHANGED
|
@@ -97,7 +97,20 @@ const MODEL_MIGRATIONS: Record<string, string> = {
|
|
|
97
97
|
|
|
98
98
|
export function loadConfig(): BotConfig {
|
|
99
99
|
if (!fs.existsSync(paths.config)) throw new Error('No config. Run `bloby init`.');
|
|
100
|
-
|
|
100
|
+
let config: BotConfig;
|
|
101
|
+
try {
|
|
102
|
+
config = JSON.parse(fs.readFileSync(paths.config, 'utf-8'));
|
|
103
|
+
} catch {
|
|
104
|
+
// Torn/truncated write — recover from the .bak mirror rather than crashing
|
|
105
|
+
// the supervisor (or, worse, letting the CLI regenerate a fresh wallet).
|
|
106
|
+
const bak = `${paths.config}.bak`;
|
|
107
|
+
if (fs.existsSync(bak)) {
|
|
108
|
+
config = JSON.parse(fs.readFileSync(bak, 'utf-8'));
|
|
109
|
+
try { saveConfig(config); } catch {}
|
|
110
|
+
} else {
|
|
111
|
+
throw new Error('config.json is corrupt and no backup (.bak) was found.');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
101
114
|
let dirty = false;
|
|
102
115
|
|
|
103
116
|
// Backward compat: migrate old { enabled: boolean } → { mode }
|
|
@@ -119,5 +132,27 @@ export function loadConfig(): BotConfig {
|
|
|
119
132
|
|
|
120
133
|
export function saveConfig(config: BotConfig): void {
|
|
121
134
|
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
122
|
-
|
|
135
|
+
// Atomic write: temp file + rename, then mirror to .bak. config.json holds the
|
|
136
|
+
// funded wallet and is written by BOTH this supervisor and the CLI, so a plain
|
|
137
|
+
// writeFileSync can be observed half-written (the Jun-9 0-byte-creds class of
|
|
138
|
+
// bug, here applied to the one file that must never be lost).
|
|
139
|
+
const json = JSON.stringify(config, null, 2);
|
|
140
|
+
const tmp = `${paths.config}.${process.pid}.tmp`;
|
|
141
|
+
try {
|
|
142
|
+
fs.writeFileSync(tmp, json);
|
|
143
|
+
try {
|
|
144
|
+
fs.renameSync(tmp, paths.config);
|
|
145
|
+
} catch (err) {
|
|
146
|
+
// Windows: rename over a file another process holds open can EPERM/EEXIST.
|
|
147
|
+
const code = (err as NodeJS.ErrnoException)?.code;
|
|
148
|
+
if (process.platform === 'win32' && (code === 'EPERM' || code === 'EEXIST')) {
|
|
149
|
+
fs.copyFileSync(tmp, paths.config);
|
|
150
|
+
fs.unlinkSync(tmp);
|
|
151
|
+
} else throw err;
|
|
152
|
+
}
|
|
153
|
+
try { fs.copyFileSync(paths.config, `${paths.config}.bak`); } catch {}
|
|
154
|
+
} catch (err) {
|
|
155
|
+
try { fs.unlinkSync(tmp); } catch {}
|
|
156
|
+
throw err;
|
|
157
|
+
}
|
|
123
158
|
}
|