@beeos-ai/cli 1.0.8 → 1.0.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beeos-ai/cli",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "type": "module",
5
5
  "description": "BeeOS CLI — run AI agents from your desktop",
6
6
  "bin": {
@@ -9,6 +9,22 @@
9
9
  `-Device` is passed. Heavier deps (Python, uv, adb, scrcpy/vnc bridges)
10
10
  are installed lazily by the CLI itself.
11
11
 
12
+ Staging / dev-loop env vars (CLI >= 1.0.10). All four are inherited by
13
+ the spawned `beeos init` / `beeos device attach` child via the normal
14
+ process env passthrough — no extra Set-Item / -PassThru is needed. CLI
15
+ >= 1.0.10 reads them via the loadOrCreateConfig env-overrides layer;
16
+ older CLIs only honor BEEOS_API_URL (telemetry) +
17
+ BEEOS_AGENT_GATEWAY_URL (runtime).
18
+
19
+ $env:BEEOS_API_URL Public API base; both this script
20
+ (telemetry) and the CLI runtime use
21
+ this.
22
+ $env:BEEOS_AGENT_GATEWAY_URL Agent Gateway base (Ed25519-signed
23
+ bootstrap requests).
24
+ $env:BEEOS_DASHBOARD_URL Dashboard base (OAuth + bind redirect).
25
+ $env:BEEOS_DEVICE_AGENT_VERSION Pin @beeos-ai/device-agent semver.
26
+ $env:BEEOS_MCP_SERVER_VERSION Pin @beeos-ai/device-mcp-server semver.
27
+
12
28
  .EXAMPLE
13
29
  # One-liner (PowerShell 5+):
14
30
  irm https://beeos.ai/install.ps1 | iex
@@ -20,6 +36,15 @@
20
36
  .EXAMPLE
21
37
  # Jump straight into device attach instead of init:
22
38
  .\install.ps1 -Device
39
+
40
+ .EXAMPLE
41
+ # Bare-metal one-shot staging install (CLI >= 1.0.10):
42
+ $env:BEEOS_API_URL = "https://public-api-staging.beeos.ai"
43
+ $env:BEEOS_AGENT_GATEWAY_URL = "https://agent-gw-staging.beeos.ai"
44
+ $env:BEEOS_DASHBOARD_URL = "https://beeos-staging-web.vercel.app"
45
+ $env:BEEOS_DEVICE_AGENT_VERSION = "0.4.1"
46
+ $env:BEEOS_MCP_SERVER_VERSION = "0.2.3"
47
+ irm https://beeos.ai/install.ps1 | iex
23
48
  #>
24
49
  param(
25
50
  [string]$Version = "latest",
@@ -33,6 +58,26 @@ $MinNodeVersion = 18
33
58
 
34
59
  $CliPackage = if ($Version -eq "latest") { "@beeos-ai/cli@latest" } else { "@beeos-ai/cli@$Version" }
35
60
 
61
+ # Sibling packages installed alongside the CLI so that `beeos device attach`
62
+ # works on a clean machine without further `npm i -g` steps (a.k.a. the
63
+ # "fleet ready" install strategy — see agents/device-agent/ARCHITECTURE.md
64
+ # for the 0.2.0+ sibling-pair topology between device-agent and
65
+ # device-mcp-server). Versions are independent of `-Version` (which only
66
+ # pins the CLI) — both packages follow their own semver. Override via
67
+ # $env:BEEOS_DEVICE_AGENT_VERSION / $env:BEEOS_MCP_SERVER_VERSION when you
68
+ # need to pin a specific build (e.g. for a staging smoke test).
69
+ $DeviceAgentVersion = if ($env:BEEOS_DEVICE_AGENT_VERSION) { $env:BEEOS_DEVICE_AGENT_VERSION } else { "latest" }
70
+ $McpServerVersion = if ($env:BEEOS_MCP_SERVER_VERSION) { $env:BEEOS_MCP_SERVER_VERSION } else { "latest" }
71
+ $DeviceAgentPackage = "@beeos-ai/device-agent@$DeviceAgentVersion"
72
+ $McpServerPackage = "@beeos-ai/device-mcp-server@$McpServerVersion"
73
+
74
+ # Telemetry endpoint (override with $env:BEEOS_API_URL for staging).
75
+ if (-not $env:BEEOS_API_URL -or $env:BEEOS_API_URL.Length -eq 0) {
76
+ $script:BeeosApiUrl = "https://public-api.beeos.ai"
77
+ } else {
78
+ $script:BeeosApiUrl = $env:BEEOS_API_URL
79
+ }
80
+
36
81
  # ── Helpers ──────────────────────────────────────────────────
37
82
 
38
83
  function Write-BeeInfo { param([string]$Msg) Write-Host "[beeos] $Msg" -ForegroundColor Cyan }
@@ -40,14 +85,87 @@ function Write-BeeWarn { param([string]$Msg) Write-Host "[beeos] $Msg" -Foregro
40
85
  function Write-BeeError { param([string]$Msg) Write-Host "[beeos] $Msg" -ForegroundColor Red }
41
86
  function Write-BeeOk { param([string]$Msg) Write-Host "[beeos] $Msg" -ForegroundColor Green }
42
87
 
88
+ # ── Script-level telemetry (mirror of install.sh send_telemetry) ──
89
+ #
90
+ # Fire-and-forget POST to `/api/v1/telemetry/install`, capped at 2 s,
91
+ # error-swallowing. Matches `web/packages/cli/src/telemetry.ts` event
92
+ # names so dashboard queries don't have to special-case Windows.
93
+ #
94
+ # Opt-out: `$env:BEEOS_NO_TELEMETRY = "1"`.
95
+ function Send-Telemetry {
96
+ param(
97
+ [string]$Event,
98
+ [string]$ErrorCode = "",
99
+ [bool]$Success = $true
100
+ )
101
+ if ($env:BEEOS_NO_TELEMETRY -eq "1") { return }
102
+
103
+ $osArch = if ($env:PROCESSOR_ARCHITEW6432) { $env:PROCESSOR_ARCHITEW6432 } else { $env:PROCESSOR_ARCHITECTURE }
104
+ if (-not $osArch) { $osArch = "unknown" }
105
+ $nodeVer = ""
106
+ try {
107
+ $raw = & node -v 2>$null
108
+ if ($raw) { $nodeVer = $raw -replace '^v', '' }
109
+ } catch {}
110
+
111
+ $hostname = $env:COMPUTERNAME
112
+ if (-not $hostname) { $hostname = "unknown" }
113
+
114
+ $payload = @{
115
+ event = $Event
116
+ cli_version = ""
117
+ dist_tag = ""
118
+ os = "win32"
119
+ arch = $osArch
120
+ node_version = $nodeVer
121
+ success = $Success
122
+ error_code = $ErrorCode
123
+ host_hint = "$hostname-$osArch"
124
+ } | ConvertTo-Json -Compress
125
+
126
+ try {
127
+ # `-TimeoutSec 2` + `-UseBasicParsing` mirrors `curl --max-time 2`.
128
+ # Errors are fully swallowed (no Stop propagation).
129
+ Invoke-RestMethod `
130
+ -Method POST `
131
+ -Uri "$($script:BeeosApiUrl.TrimEnd('/'))/api/v1/telemetry/install" `
132
+ -ContentType "application/json" `
133
+ -Body $payload `
134
+ -TimeoutSec 2 `
135
+ -UseBasicParsing `
136
+ -ErrorAction SilentlyContinue | Out-Null
137
+ } catch {
138
+ # Telemetry MUST NOT break installs.
139
+ }
140
+ }
141
+
43
142
  # ── Platform detection ───────────────────────────────────────
44
143
 
45
144
  function Test-Platform {
46
145
  $arch = $env:PROCESSOR_ARCHITECTURE
47
146
  $effectiveArch = if ($env:PROCESSOR_ARCHITEW6432) { $env:PROCESSOR_ARCHITEW6432 } else { $arch }
48
147
  Write-BeeInfo "detected: win32 $effectiveArch"
148
+
149
+ # Architecture policy (kept in sync with install.sh):
150
+ # - Non-interactive: hard exit 2 — silent warnings are invisible
151
+ # in piped installs (`irm | iex`) and would trap users on a
152
+ # platform we don't ship native sidecars for.
153
+ # - Interactive: warn and require explicit confirmation.
49
154
  if ($effectiveArch -notin @("AMD64","ARM64","x86_64")) {
50
- Write-BeeWarn "Unusual CPU architecture: $effectiveArch. Some sidecar binaries may be unavailable."
155
+ $reason = "Unsupported Windows architecture: $effectiveArch (need AMD64 or ARM64)"
156
+ Write-BeeWarn $reason
157
+ if (-not [Environment]::UserInteractive) {
158
+ Send-Telemetry -Event "install.bootstrap.arch_fail" -ErrorCode $reason -Success $false
159
+ Write-BeeError "Non-interactive shell — aborting."
160
+ exit 2
161
+ }
162
+ $go = Read-Host "Continue anyway? [y/N]"
163
+ if ($go -notmatch '^(y|yes)$') {
164
+ Send-Telemetry -Event "install.bootstrap.arch_fail" -ErrorCode $reason -Success $false
165
+ Write-BeeError "Aborting per user."
166
+ exit 2
167
+ }
168
+ Write-BeeInfo "Proceeding on unsupported arch by user confirmation."
51
169
  }
52
170
  }
53
171
 
@@ -174,6 +292,39 @@ function Read-ExistingInstallAction {
174
292
  }
175
293
  }
176
294
 
295
+ # ── Device-agent sibling suite installer ─────────────────────
296
+ #
297
+ # Installs `@beeos-ai/device-agent` + `@beeos-ai/device-mcp-server` globally
298
+ # so the very first `beeos device attach` on a clean machine does not need
299
+ # to fetch them separately. `@beeos-ai/device-common` is pulled
300
+ # transitively as a `dependencies` entry of both — no separate install
301
+ # needed.
302
+ #
303
+ # Failure here is NEVER fatal: the CLI itself was already installed
304
+ # successfully, and `beeos device attach` will prompt to install missing
305
+ # pieces via ensureDeviceAgent on first use. We just emit a WARN with the
306
+ # manual recovery command.
307
+ function Install-DeviceAgentSuite {
308
+ $npmPath = Get-Command npm -ErrorAction SilentlyContinue
309
+ if (-not $npmPath) {
310
+ Write-BeeWarn "npm not available — skipping device-agent suite install."
311
+ Write-BeeWarn "Manual fix: install Node + run ``npm i -g $DeviceAgentPackage $McpServerPackage``."
312
+ return
313
+ }
314
+
315
+ Write-BeeInfo "Installing device-agent suite ($DeviceAgentPackage, $McpServerPackage)..."
316
+ & npm install -g $DeviceAgentPackage $McpServerPackage
317
+ if ($LASTEXITCODE -eq 0) {
318
+ Write-BeeOk "device-agent suite installed."
319
+ } else {
320
+ Write-BeeWarn "Failed to install device-agent suite — ``beeos device attach`` will prompt for it on first use."
321
+ Write-BeeWarn "Manual fix: npm i -g $DeviceAgentPackage $McpServerPackage"
322
+ # Reset $LASTEXITCODE so subsequent `if ($LASTEXITCODE -ne 0)` checks
323
+ # in callers don't trip on this non-fatal failure.
324
+ $global:LASTEXITCODE = 0
325
+ }
326
+ }
327
+
177
328
  # ── Run CLI ──────────────────────────────────────────────────
178
329
 
179
330
  function Invoke-BeeosCli {
@@ -187,6 +338,11 @@ function Invoke-BeeosCli {
187
338
  if ($ExtraArgs) { $args += $ExtraArgs }
188
339
 
189
340
  if ($npxPath) {
341
+ # Temporary / npx path: deliberately do NOT install the device-agent
342
+ # suite globally. The user opted into the throwaway-install flow, so
343
+ # we honor that — if they later run `beeos device attach`,
344
+ # ensureDeviceAgent will guide them through a global install at that
345
+ # point.
190
346
  & npx --yes $CliPackage @args
191
347
  } else {
192
348
  Write-BeeWarn "npx not found, installing globally..."
@@ -200,6 +356,7 @@ function Invoke-BeeosCli {
200
356
  Write-BeeError " - EACCES / permission error: run PowerShell as Administrator"
201
357
  exit 1
202
358
  }
359
+ Install-DeviceAgentSuite
203
360
  & beeos @args
204
361
  }
205
362
  }
@@ -212,13 +369,16 @@ Write-Host " https://beeos.ai" -ForegroundColor DarkGray
212
369
  Write-Host ""
213
370
 
214
371
  Test-Platform
372
+ Send-Telemetry -Event "install.bootstrap.start"
215
373
 
216
374
  if (-not (Test-NodeVersion)) {
217
375
  if (-not (Install-NodeAuto)) {
376
+ Send-Telemetry -Event "install.bootstrap.node_fail" -ErrorCode "node_install_failed" -Success $false
218
377
  Show-InstallHints
219
378
  exit 1
220
379
  }
221
380
  if (-not (Test-NodeVersion)) {
381
+ Send-Telemetry -Event "install.bootstrap.node_fail" -ErrorCode "node_version_too_old" -Success $false
222
382
  Show-InstallHints
223
383
  exit 1
224
384
  }
@@ -247,6 +407,7 @@ if ($beeosCmd) {
247
407
  Write-BeeError " - EACCES / permission error: run PowerShell as Administrator"
248
408
  exit 1
249
409
  }
410
+ Install-DeviceAgentSuite
250
411
  }
251
412
  "rerun" {
252
413
  Write-BeeInfo "Reusing existing install."
@@ -257,6 +418,8 @@ if ($beeosCmd) {
257
418
  Write-BeeInfo "Running BeeOS CLI..."
258
419
  Write-Host ""
259
420
 
421
+ Send-Telemetry -Event "install.bootstrap.success"
422
+
260
423
  if ($Device) {
261
424
  Invoke-BeeosCli -Subcommand "device" -ExtraArgs (@("attach") + $Passthrough)
262
425
  } else {
@@ -15,6 +15,31 @@
15
15
  # upgrade / rebind / skip before re-running the bind flow.
16
16
  # 4. Dispatch into `beeos init` (default) or `beeos device attach` when
17
17
  # `--device` is passed.
18
+ #
19
+ # ── Staging / dev-loop env vars (CLI ≥ 1.0.10) ──────────────────────
20
+ # All four are inherited by the spawned `beeos init` / `beeos device
21
+ # attach` child via the normal `exec` env passthrough — there is no
22
+ # extra `export` needed. CLI ≥ 1.0.10 reads them via the `loadOrCreate
23
+ # Config` env-overrides layer; older CLIs only honor BEEOS_API_URL
24
+ # (telemetry) + BEEOS_AGENT_GATEWAY_URL (runtime).
25
+ #
26
+ # BEEOS_API_URL Public API base; both the script (telemetry)
27
+ # and the CLI runtime use this.
28
+ # BEEOS_AGENT_GATEWAY_URL Agent Gateway base (Ed25519-signed
29
+ # bootstrap requests).
30
+ # BEEOS_DASHBOARD_URL Dashboard base (OAuth + bind redirect).
31
+ # BEEOS_DEVICE_AGENT_VERSION Pin @beeos-ai/device-agent semver.
32
+ # BEEOS_MCP_SERVER_VERSION Pin @beeos-ai/device-mcp-server semver.
33
+ #
34
+ # Example: bare-metal one-shot staging install, including device-agent
35
+ # version pin and OAuth landing on the staging dashboard:
36
+ #
37
+ # BEEOS_API_URL=https://public-api-staging.beeos.ai \
38
+ # BEEOS_AGENT_GATEWAY_URL=https://agent-gw-staging.beeos.ai \
39
+ # BEEOS_DASHBOARD_URL=https://beeos-staging-web.vercel.app \
40
+ # BEEOS_DEVICE_AGENT_VERSION=0.4.1 \
41
+ # BEEOS_MCP_SERVER_VERSION=0.2.3 \
42
+ # curl -fsSL https://beeos.ai/install | bash -s -- --version 1.0.10
18
43
 
19
44
  set -euo pipefail
20
45
 
@@ -27,17 +52,103 @@ RED="\033[31m"
27
52
  CYAN="\033[36m"
28
53
 
29
54
  CLI_PACKAGE="@beeos-ai/cli@latest"
55
+ # Sibling packages installed alongside the CLI so that `beeos device attach`
56
+ # works on a clean machine without further `npm i -g` steps (a.k.a. the
57
+ # "fleet ready" install strategy — see agents/device-agent/ARCHITECTURE.md
58
+ # for the 0.2.0+ sibling-pair topology between device-agent and
59
+ # device-mcp-server). Their versions are independent of `--version` (which
60
+ # only pins the CLI) — both packages follow their own semver. Override via
61
+ # BEEOS_DEVICE_AGENT_VERSION / BEEOS_MCP_SERVER_VERSION when you need to
62
+ # pin a specific build (e.g. for a staging smoke test).
63
+ DEVICE_AGENT_PACKAGE="@beeos-ai/device-agent@${BEEOS_DEVICE_AGENT_VERSION:-latest}"
64
+ MCP_SERVER_PACKAGE="@beeos-ai/device-mcp-server@${BEEOS_MCP_SERVER_VERSION:-latest}"
30
65
  MIN_NODE_VERSION=18
31
66
  NVM_VERSION="v0.40.1"
32
67
  FNM_INSTALL_URL="https://fnm.vercel.app/install"
33
68
 
34
69
  BEEOS_HOME="${BEEOS_HOME:-$HOME/.beeos}"
70
+ # Default telemetry endpoint. `$BEEOS_API_URL` lets developers aim at
71
+ # staging / a local gateway; empty-or-unset falls back to production.
72
+ BEEOS_API_URL="${BEEOS_API_URL:-https://public-api.beeos.ai}"
73
+
74
+ # Where the installer writes verbose stderr for Node auto-install.
75
+ # Overridable via `BEEOS_INSTALL_LOG=/path/to/file.log` so support can
76
+ # ask "run the one-liner with `BEEOS_INSTALL_LOG=~/beeos-install.log`
77
+ # and share the log" rather than asking the user to recreate the
78
+ # failure with manual `bash -x`.
79
+ #
80
+ # Pre-P1-12 we buried every `2>/dev/null` in `try_install_node`, which
81
+ # meant bug reports like "nvm install --lts failed" were unreproducible
82
+ # from anything but the user's terminal. We now preserve stderr in a
83
+ # per-PID file and reference it in the terminal hint on failure.
84
+ BEEOS_INSTALL_LOG="${BEEOS_INSTALL_LOG:-/tmp/beeos-install-$$.log}"
85
+ # Truncate on every run so the log reflects only the current attempt.
86
+ : > "$BEEOS_INSTALL_LOG" 2>/dev/null || true
35
87
 
36
88
  info() { printf "${BOLD}${CYAN}[beeos]${RESET} %s\n" "$*"; }
37
89
  warn() { printf "${BOLD}${YELLOW}[beeos]${RESET} %s\n" "$*"; }
38
90
  error() { printf "${BOLD}${RED}[beeos]${RESET} %s\n" "$*" >&2; }
39
91
  success() { printf "${BOLD}${GREEN}[beeos]${RESET} %s\n" "$*"; }
40
92
 
93
+ # ── Telemetry (script-level, pre-CLI) ────────────────────────
94
+ #
95
+ # Mirrors `web/packages/cli/src/telemetry.ts` so we can capture
96
+ # failures that happen BEFORE the Node CLI even starts (e.g. Node
97
+ # install failure, unsupported arch). Fire-and-forget with a 2 s cap —
98
+ # we must never block a real install on a flaky telemetry endpoint.
99
+ #
100
+ # Event names used here:
101
+ # - install.bootstrap.start — script entered `main()`
102
+ # - install.bootstrap.node_fail — could not install/find Node
103
+ # - install.bootstrap.arch_fail — unsupported arch
104
+ # - install.bootstrap.success — about to exec `beeos`
105
+ #
106
+ # `BEEOS_NO_TELEMETRY=1` short-circuits everything. This matches the
107
+ # Node CLI's opt-out env var verbatim so a user who disabled telemetry
108
+ # for the CLI doesn't unexpectedly get events from the bootstrap.
109
+ send_telemetry() {
110
+ local event="$1"
111
+ local error_code="${2:-}"
112
+ local success_flag="${3:-true}"
113
+
114
+ if [[ "${BEEOS_NO_TELEMETRY:-}" == "1" ]]; then
115
+ return 0
116
+ fi
117
+ if ! command -v curl &>/dev/null; then
118
+ return 0
119
+ fi
120
+
121
+ local os_kind="${OS_KIND:-unknown}"
122
+ local os_arch="${OS_ARCH:-unknown}"
123
+ local node_ver=""
124
+ if command -v node &>/dev/null; then
125
+ node_ver=$(node -v 2>/dev/null | sed 's/^v//' || true)
126
+ fi
127
+ local host_hint
128
+ host_hint="$(hostname 2>/dev/null || echo unknown)-${os_arch}"
129
+
130
+ # POST body kept deliberately small — matches Node CLI payload.
131
+ # Note: `cli_version` is empty until after npx install; we fill it
132
+ # only when the bootstrap telemetry is fired post-install, from the
133
+ # Node side. For pre-install events it stays "" and that's fine.
134
+ local body
135
+ body=$(cat <<JSON
136
+ {"event":"${event}","cli_version":"","dist_tag":"","os":"${os_kind}","arch":"${os_arch}","node_version":"${node_ver}","success":${success_flag},"error_code":"${error_code}","host_hint":"${host_hint}"}
137
+ JSON
138
+ )
139
+
140
+ # `--max-time 2` caps the whole request at 2 s. `-o /dev/null -s`
141
+ # silences output. Swallow non-zero exit — telemetry MUST NOT break
142
+ # an install.
143
+ curl -fsS -o /dev/null \
144
+ --max-time 2 \
145
+ -H "Content-Type: application/json" \
146
+ -X POST \
147
+ -d "$body" \
148
+ "${BEEOS_API_URL%/}/api/v1/telemetry/install" \
149
+ 2>/dev/null || true
150
+ }
151
+
41
152
  # ── Parse arguments ──────────────────────────────────────────
42
153
 
43
154
  CLI_VERSION=""
@@ -102,17 +213,46 @@ detect_platform() {
102
213
 
103
214
  info "detected: ${OS_KIND} ${OS_ARCH}${LIBC_KIND:+ (${LIBC_KIND})}"
104
215
 
216
+ # Architecture policy (kept in sync with install.ps1):
217
+ # - Non-interactive (no TTY): hard exit 2. The user cannot see a
218
+ # warning scroll past, and we must not silently install a CLI
219
+ # that will later fail to download its native sidecars.
220
+ # - Interactive TTY: warn and ask for confirmation. Power users
221
+ # sometimes run on odd arches (RISC-V Linux, IBM z, early
222
+ # Windows-on-ARM) for experimentation; bail out by default, but
223
+ # let them override when they know what they're doing.
224
+ local interactive=0
225
+ if [[ -t 0 ]] || [[ -t 1 ]]; then interactive=1; fi
226
+ local arch_bail arch_confirm
227
+ arch_bail() {
228
+ local reason="$1"
229
+ error "$reason"
230
+ send_telemetry "install.bootstrap.arch_fail" "$reason" false
231
+ exit 2
232
+ }
233
+ arch_confirm() {
234
+ local reason="$1"
235
+ warn "$reason"
236
+ if (( interactive == 0 )); then
237
+ arch_bail "$reason"
238
+ fi
239
+ local yn=""
240
+ read -rp "Continue anyway? [y/N]: " yn </dev/tty 2>/dev/null || yn=""
241
+ case "${yn,,}" in
242
+ y|yes) info "Proceeding on unsupported arch by user confirmation." ;;
243
+ *) arch_bail "$reason" ;;
244
+ esac
245
+ }
246
+
105
247
  case "$OS_KIND" in
106
248
  darwin)
107
249
  if [[ "$OS_ARCH" != "arm64" && "$OS_ARCH" != "x86_64" ]]; then
108
- error "Unsupported macOS architecture: $OS_ARCH (need arm64 or x86_64)"
109
- exit 2
250
+ arch_confirm "Unsupported macOS architecture: $OS_ARCH (need arm64 or x86_64)"
110
251
  fi
111
252
  ;;
112
253
  linux)
113
254
  if [[ "$OS_ARCH" != "arm64" && "$OS_ARCH" != "x86_64" ]]; then
114
- error "Unsupported Linux architecture: $OS_ARCH (need arm64 or x86_64)"
115
- exit 2
255
+ arch_confirm "Unsupported Linux architecture: $OS_ARCH (need arm64 or x86_64)"
116
256
  fi
117
257
  if [[ "$LIBC_KIND" == "musl" ]]; then
118
258
  # No musl prebuilt for scrcpy-bridge / vnc-bridge yet. The CLI
@@ -122,8 +262,7 @@ detect_platform() {
122
262
  fi
123
263
  ;;
124
264
  *)
125
- error "Unsupported OS: $OS_KIND (use install.ps1 for Windows)"
126
- exit 2
265
+ arch_bail "Unsupported OS: $OS_KIND (use install.ps1 for Windows)"
127
266
  ;;
128
267
  esac
129
268
  }
@@ -151,15 +290,26 @@ check_node() {
151
290
 
152
291
  try_install_node() {
153
292
  info "Node.js not found or too old. Attempting automatic installation..."
293
+ info "Verbose output appended to: ${BEEOS_INSTALL_LOG}"
154
294
  echo ""
155
295
 
296
+ # Every step below redirects stderr to $BEEOS_INSTALL_LOG (append)
297
+ # so that a post-mortem has the full picture. We deliberately do NOT
298
+ # use bare `2>/dev/null` anymore — it's the single biggest source of
299
+ # unactionable bug reports in this layer. Steps still short-circuit
300
+ # on success to avoid N-second retries when one tool works.
301
+ {
302
+ echo "=== try_install_node @ $(date -u +%FT%TZ) ==="
303
+ echo "OS_KIND=${OS_KIND} OS_ARCH=${OS_ARCH} LIBC_KIND=${LIBC_KIND:-}"
304
+ } >> "$BEEOS_INSTALL_LOG" 2>&1 || true
305
+
156
306
  # 1. Existing nvm
157
307
  export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"
158
308
  if [[ -s "$NVM_DIR/nvm.sh" ]]; then
159
309
  info "Loading existing nvm..."
160
310
  # shellcheck source=/dev/null
161
311
  . "$NVM_DIR/nvm.sh"
162
- if nvm install --lts 2>/dev/null; then
312
+ if nvm install --lts >> "$BEEOS_INSTALL_LOG" 2>&1; then
163
313
  success "Node.js $(node -v) installed via existing nvm"
164
314
  return 0
165
315
  fi
@@ -167,10 +317,11 @@ try_install_node() {
167
317
 
168
318
  # 2. Fresh nvm install
169
319
  info "Installing nvm ${NVM_VERSION}..."
170
- if curl -fsSL "https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_VERSION}/install.sh" | bash 2>/dev/null; then
320
+ if curl -fsSL "https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_VERSION}/install.sh" 2>>"$BEEOS_INSTALL_LOG" \
321
+ | bash >> "$BEEOS_INSTALL_LOG" 2>&1; then
171
322
  # shellcheck source=/dev/null
172
- . "$NVM_DIR/nvm.sh" 2>/dev/null || true
173
- if nvm install --lts 2>/dev/null; then
323
+ . "$NVM_DIR/nvm.sh" >> "$BEEOS_INSTALL_LOG" 2>&1 || true
324
+ if nvm install --lts >> "$BEEOS_INSTALL_LOG" 2>&1; then
174
325
  success "Node.js $(node -v) installed via nvm"
175
326
  return 0
176
327
  fi
@@ -179,10 +330,12 @@ try_install_node() {
179
330
  # 3. fnm — works on locked-down Linux without git/curl quirks
180
331
  if [[ "$OS_KIND" == "linux" ]] && ! command -v fnm &>/dev/null; then
181
332
  info "Trying fnm (single-user, no sudo)..."
182
- if curl -fsSL "$FNM_INSTALL_URL" | bash -s -- --skip-shell 2>/dev/null; then
333
+ if curl -fsSL "$FNM_INSTALL_URL" 2>>"$BEEOS_INSTALL_LOG" \
334
+ | bash -s -- --skip-shell >> "$BEEOS_INSTALL_LOG" 2>&1; then
183
335
  export PATH="$HOME/.local/share/fnm:$PATH"
184
- eval "$(fnm env --shell bash 2>/dev/null)" || true
185
- if fnm install --lts 2>/dev/null && fnm use lts-latest 2>/dev/null; then
336
+ eval "$(fnm env --shell bash 2>>"$BEEOS_INSTALL_LOG")" || true
337
+ if fnm install --lts >> "$BEEOS_INSTALL_LOG" 2>&1 \
338
+ && fnm use lts-latest >> "$BEEOS_INSTALL_LOG" 2>&1; then
186
339
  success "Node.js $(node -v) installed via fnm"
187
340
  return 0
188
341
  fi
@@ -192,7 +345,7 @@ try_install_node() {
192
345
  # 4. macOS Homebrew
193
346
  if [[ "$OS_KIND" == "darwin" ]] && command -v brew &>/dev/null; then
194
347
  info "Trying Homebrew..."
195
- if brew install node 2>/dev/null; then
348
+ if brew install node >> "$BEEOS_INSTALL_LOG" 2>&1; then
196
349
  success "Node.js installed via Homebrew"
197
350
  return 0
198
351
  fi
@@ -205,6 +358,7 @@ try_install_node() {
205
358
 
206
359
  print_install_hints() {
207
360
  error "Could not install Node.js automatically."
361
+ error "See ${BEEOS_INSTALL_LOG} for full stderr of every attempted method."
208
362
  echo ""
209
363
  info "Please install Node.js >= ${MIN_NODE_VERSION} manually:"
210
364
  echo ""
@@ -294,14 +448,60 @@ prompt_existing_install_action() {
294
448
  esac
295
449
  }
296
450
 
451
+ # ── Device-agent sibling suite installer ─────────────────────
452
+ #
453
+ # Installs `@beeos-ai/device-agent` + `@beeos-ai/device-mcp-server` globally
454
+ # so the very first `beeos device attach` on a clean machine does not need
455
+ # to fetch them separately. `@beeos-ai/device-common` is pulled
456
+ # transitively as a `dependencies` entry of both — no separate install
457
+ # needed.
458
+ #
459
+ # Failure here is NEVER fatal: the CLI itself was already installed
460
+ # successfully, and `beeos device attach` will prompt to install missing
461
+ # pieces via ensureDeviceAgent on first use. We just emit a WARN with the
462
+ # manual recovery command.
463
+ install_device_agent_suite() {
464
+ if ! command -v npm &>/dev/null; then
465
+ warn "npm not available — skipping device-agent suite install."
466
+ warn "Manual fix: install Node + run \`npm i -g ${DEVICE_AGENT_PACKAGE} ${MCP_SERVER_PACKAGE}\`."
467
+ return 0
468
+ fi
469
+
470
+ info "Installing device-agent suite (${DEVICE_AGENT_PACKAGE}, ${MCP_SERVER_PACKAGE})..."
471
+ if npm install -g "$DEVICE_AGENT_PACKAGE" "$MCP_SERVER_PACKAGE"; then
472
+ success "device-agent suite installed."
473
+ else
474
+ warn "Failed to install device-agent suite — \`beeos device attach\` will prompt for it on first use."
475
+ warn "Manual fix: npm i -g ${DEVICE_AGENT_PACKAGE} ${MCP_SERVER_PACKAGE}"
476
+ fi
477
+ return 0
478
+ }
479
+
297
480
  # ── Main ─────────────────────────────────────────────────────
298
481
 
299
482
  run_cli() {
300
483
  local subcmd="$1"
301
484
  shift || true
302
485
 
486
+ # When invoked as `curl ... | bash`, our own stdin is a pipe that has
487
+ # already been fully consumed by the time we exec the CLI. Node's
488
+ # readline treats a closed / non-TTY stdin as "no answer", silently
489
+ # collapsing every interactive prompt to its default. Reopen stdin
490
+ # against the controlling terminal so `beeos init` can still ask for
491
+ # framework choice, bind confirmation, etc. `-t 1` guards us against
492
+ # true headless runs (CI) where /dev/tty doesn't exist.
493
+ local stdin_src="/dev/stdin"
494
+ if [ -t 1 ] && [ -e /dev/tty ]; then
495
+ stdin_src="/dev/tty"
496
+ fi
497
+
303
498
  if command -v npx &>/dev/null; then
304
- exec npx --yes "$CLI_PACKAGE" "$subcmd" "$@"
499
+ # Temporary / npx path: deliberately do NOT install the device-agent
500
+ # suite globally. The user opted into the throwaway-install flow, so
501
+ # we honor that — if they later run `beeos device attach`,
502
+ # ensureDeviceAgent will guide them through a global install at that
503
+ # point.
504
+ exec npx --yes "$CLI_PACKAGE" "$subcmd" "$@" <"$stdin_src"
305
505
  else
306
506
  warn "npx not found, installing globally..."
307
507
  if ! npm install -g "$CLI_PACKAGE"; then
@@ -314,7 +514,8 @@ run_cli() {
314
514
  error " use a node version manager (nvm / fnm) or sudo"
315
515
  exit 1
316
516
  fi
317
- exec beeos "$subcmd" "$@"
517
+ install_device_agent_suite
518
+ exec beeos "$subcmd" "$@" <"$stdin_src"
318
519
  fi
319
520
  }
320
521
 
@@ -325,13 +526,16 @@ main() {
325
526
  echo ""
326
527
 
327
528
  detect_platform
529
+ send_telemetry "install.bootstrap.start"
328
530
 
329
531
  if ! check_node; then
330
532
  if ! try_install_node; then
533
+ send_telemetry "install.bootstrap.node_fail" "node_install_failed" false
331
534
  print_install_hints
332
535
  exit 1
333
536
  fi
334
537
  if ! check_node; then
538
+ send_telemetry "install.bootstrap.node_fail" "node_version_too_old" false
335
539
  print_install_hints
336
540
  exit 1
337
541
  fi
@@ -361,6 +565,7 @@ main() {
361
565
  error " use a node version manager (nvm / fnm) or sudo"
362
566
  exit 1
363
567
  fi
568
+ install_device_agent_suite
364
569
  fi
365
570
  ;;
366
571
  rerun)
@@ -372,6 +577,8 @@ main() {
372
577
  info "Running BeeOS CLI..."
373
578
  echo ""
374
579
 
580
+ send_telemetry "install.bootstrap.success"
581
+
375
582
  if [[ "$MODE" == "device" ]]; then
376
583
  run_cli "device" "attach" "${PASSTHROUGH_ARGS[@]+"${PASSTHROUGH_ARGS[@]}"}"
377
584
  else