@beeos-ai/cli 1.0.13 → 1.0.15
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/dist/index.js +1773 -334
- package/package.json +2 -2
- package/scripts/_existing_install_actions.json +40 -0
- package/scripts/install.Tests.ps1 +169 -0
- package/scripts/install.ps1 +252 -9
- package/scripts/install.sh +61 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@beeos-ai/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.15",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "BeeOS CLI — run AI agents from your desktop",
|
|
6
6
|
"bin": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"build": "tsup",
|
|
19
19
|
"type-check": "tsc --noEmit",
|
|
20
20
|
"lint": "eslint src/",
|
|
21
|
-
"test": "vitest run
|
|
21
|
+
"test": "vitest run",
|
|
22
22
|
"prepublishOnly": "pnpm build"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": [
|
|
3
|
+
"P1-E of the install-link review (shared anchor file).",
|
|
4
|
+
"",
|
|
5
|
+
"Three call sites render the 'BeeOS is already installed — what now?'",
|
|
6
|
+
"menu with subtly different copy:",
|
|
7
|
+
"",
|
|
8
|
+
" 1. web/packages/cli/scripts/install.sh (bash, pre-Node)",
|
|
9
|
+
" 2. web/packages/cli/scripts/install.ps1 (PowerShell, pre-Node)",
|
|
10
|
+
" 3. web/packages/cli/src/commands/init.ts (TS, post-Node)",
|
|
11
|
+
"",
|
|
12
|
+
"Pre-Node sites can't import TypeScript constants, so we anchor the",
|
|
13
|
+
"wording with this manifest plus a vitest grep lint",
|
|
14
|
+
"(`web/packages/cli/src/__tests__/existing-install-actions.test.ts`).",
|
|
15
|
+
"If you change a label in any of the three files, update the",
|
|
16
|
+
"matching `keywords` here so the lint stays representative; if you",
|
|
17
|
+
"add a new action altogether, add a new entry below + render it",
|
|
18
|
+
"in all three sites."
|
|
19
|
+
],
|
|
20
|
+
"actions": {
|
|
21
|
+
"upgrade": {
|
|
22
|
+
"$comment": "Reinstall the CLI to the latest version, then proceed. install.sh/install.ps1 wording differs slightly from init.ts which says 'Upgrade CLI & agents'; both must hit at least one keyword from `keywords`.",
|
|
23
|
+
"keywords": ["Reinstall", "Upgrade"]
|
|
24
|
+
},
|
|
25
|
+
"rerun": {
|
|
26
|
+
"$comment": "Reuse the existing install (no npm i -g). install.sh/install.ps1 say 'Re-run beeos init', init.ts shows the 'Re-bind' wording for the same conceptual action.",
|
|
27
|
+
"keywords": ["Re-run", "Re-bind", "Reusing"]
|
|
28
|
+
},
|
|
29
|
+
"skip": {
|
|
30
|
+
"$comment": "Do nothing. Stable wording across all three sites.",
|
|
31
|
+
"keywords": ["Skip"]
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"$files_root_comment": "Paths below are relative to the `web/` workspace root (the test resolves them against that anchor).",
|
|
35
|
+
"files": [
|
|
36
|
+
"packages/cli/scripts/install.sh",
|
|
37
|
+
"packages/cli/scripts/install.ps1",
|
|
38
|
+
"packages/core/src/detect.ts"
|
|
39
|
+
]
|
|
40
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Pester smoke test for install.ps1 (P2-2 of the install-link review).
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
The goal of this suite is NOT end-to-end coverage of the install
|
|
7
|
+
flow — that requires a real Windows host with elevated privileges
|
|
8
|
+
plus npm/winget/choco mocked, which we do not yet have in CI. The
|
|
9
|
+
smoke test instead asserts the script's structural integrity:
|
|
10
|
+
|
|
11
|
+
1. install.ps1 parses with no syntax errors (caught by the
|
|
12
|
+
AST parser before any side effect).
|
|
13
|
+
2. The required helper functions are defined at top level so
|
|
14
|
+
that future refactors don't accidentally drop them.
|
|
15
|
+
3. The dry-run hook (`BEEOS_INSTALL_DRY_RUN=1`) exits before
|
|
16
|
+
performing any network or filesystem write.
|
|
17
|
+
|
|
18
|
+
These three properties together make sure that regressions of the
|
|
19
|
+
"install.sh / install.ps1 没有自动化测试" type from the
|
|
20
|
+
install-link review get caught at PR time on the
|
|
21
|
+
install-script-lint workflow.
|
|
22
|
+
#>
|
|
23
|
+
|
|
24
|
+
# ── Parse-time checks ────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
$scriptPath = Join-Path $PSScriptRoot "install.ps1"
|
|
27
|
+
|
|
28
|
+
Describe "install.ps1 parse-time integrity" {
|
|
29
|
+
It "exists at the expected path" {
|
|
30
|
+
Test-Path $scriptPath | Should -Be $true
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
It "parses without syntax errors (AST round-trip)" {
|
|
34
|
+
$tokens = $null
|
|
35
|
+
$errors = $null
|
|
36
|
+
[System.Management.Automation.Language.Parser]::ParseFile(
|
|
37
|
+
$scriptPath,
|
|
38
|
+
[ref]$tokens,
|
|
39
|
+
[ref]$errors
|
|
40
|
+
) | Out-Null
|
|
41
|
+
$errors.Count | Should -Be 0
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
It "declares the expected helper functions" {
|
|
45
|
+
$tokens = $null
|
|
46
|
+
$errors = $null
|
|
47
|
+
$ast = [System.Management.Automation.Language.Parser]::ParseFile(
|
|
48
|
+
$scriptPath,
|
|
49
|
+
[ref]$tokens,
|
|
50
|
+
[ref]$errors
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
$functions = $ast.FindAll({
|
|
54
|
+
param($node)
|
|
55
|
+
$node -is [System.Management.Automation.Language.FunctionDefinitionAst]
|
|
56
|
+
}, $true) | ForEach-Object { $_.Name }
|
|
57
|
+
|
|
58
|
+
$expected = @(
|
|
59
|
+
"Write-BeeInfo",
|
|
60
|
+
"Write-BeeOk",
|
|
61
|
+
"Write-BeeWarn",
|
|
62
|
+
"Write-BeeError",
|
|
63
|
+
"Test-Platform",
|
|
64
|
+
"Test-NodeVersion",
|
|
65
|
+
"Install-NodeAuto",
|
|
66
|
+
"Show-InstallHints",
|
|
67
|
+
"Send-Telemetry",
|
|
68
|
+
"Read-ExistingInstallAction",
|
|
69
|
+
"Install-DeviceAgentSuite",
|
|
70
|
+
"Invoke-BeeosCli",
|
|
71
|
+
# P2-8: PSCore + RunAs auto-elevation helpers.
|
|
72
|
+
"Test-IsAdministrator",
|
|
73
|
+
"Request-Elevation"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
foreach ($name in $expected) {
|
|
77
|
+
$functions | Should -Contain $name
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
# ── Static guards ────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
Describe "install.ps1 platform guard (P2-8)" {
|
|
85
|
+
It "contains the non-Windows early-exit branch" {
|
|
86
|
+
$content = Get-Content -Raw -Path $scriptPath
|
|
87
|
+
# Look for the marker comment AND the exit branch so a
|
|
88
|
+
# superficial rename of the variable still trips the test.
|
|
89
|
+
$content | Should -Match "This installer is Windows-only"
|
|
90
|
+
$content | Should -Match "exit 2"
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
It "contains a UAC auto-elevation branch" {
|
|
94
|
+
$content = Get-Content -Raw -Path $scriptPath
|
|
95
|
+
$content | Should -Match "Start-Process .* -Verb RunAs"
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
It "honours BEEOS_NO_AUTO_ELEVATE=1 as an opt-out" {
|
|
99
|
+
$content = Get-Content -Raw -Path $scriptPath
|
|
100
|
+
$content | Should -Match "BEEOS_NO_AUTO_ELEVATE"
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
Describe "install.ps1 stdin / TTY handover (P1-J)" {
|
|
105
|
+
# When stdin is piped (`irm ... | iex`), the child `beeos init`'s
|
|
106
|
+
# prompts read from the exhausted pipe and silently take defaults.
|
|
107
|
+
# Mirrors install.sh's `</dev/tty` reattachment.
|
|
108
|
+
It "detects [Console]::IsInputRedirected" {
|
|
109
|
+
$content = Get-Content -Raw -Path $scriptPath
|
|
110
|
+
$content | Should -Match "\[Console\]::IsInputRedirected"
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
It "uses Start-Process with -NoNewWindow when stdin is redirected" {
|
|
114
|
+
$content = Get-Content -Raw -Path $scriptPath
|
|
115
|
+
$content | Should -Match "Start-Process .* -NoNewWindow"
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
Describe "install.ps1 stderr log file (P1-I)" {
|
|
120
|
+
# Mirrors install.sh's BEEOS_INSTALL_LOG: winget / choco stderr
|
|
121
|
+
# MUST land in a forensic file instead of `2>$null`. The lint
|
|
122
|
+
# below is structural — assert the variable + the redirect appear.
|
|
123
|
+
It "declares a `$BeeosInstallLog` variable" {
|
|
124
|
+
$content = Get-Content -Raw -Path $scriptPath
|
|
125
|
+
$content | Should -Match "BeeosInstallLog"
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
It "redirects winget stderr to the install log" {
|
|
129
|
+
$content = Get-Content -Raw -Path $scriptPath
|
|
130
|
+
# `2>>$BeeosInstallLog` is the canonical append-redirect syntax;
|
|
131
|
+
# `2>$null` here would be a regression to the silent
|
|
132
|
+
# behaviour the review called out.
|
|
133
|
+
$content | Should -Match "winget install [^`r`n]+2>>\$BeeosInstallLog"
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
It "redirects choco stderr to the install log" {
|
|
137
|
+
$content = Get-Content -Raw -Path $scriptPath
|
|
138
|
+
$content | Should -Match "choco install [^`r`n]+2>>\$BeeosInstallLog"
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
It "Show-InstallHints surfaces the log path on failure" {
|
|
142
|
+
$content = Get-Content -Raw -Path $scriptPath
|
|
143
|
+
$content | Should -Match "Installer log:"
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
# ── Dry-run hook ─────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
Describe "install.ps1 dry-run hook" {
|
|
150
|
+
BeforeEach {
|
|
151
|
+
$script:savedDryRun = $env:BEEOS_INSTALL_DRY_RUN
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
AfterEach {
|
|
155
|
+
if ($null -eq $script:savedDryRun) {
|
|
156
|
+
Remove-Item Env:BEEOS_INSTALL_DRY_RUN -ErrorAction SilentlyContinue
|
|
157
|
+
} else {
|
|
158
|
+
$env:BEEOS_INSTALL_DRY_RUN = $script:savedDryRun
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
It "exits cleanly when BEEOS_INSTALL_DRY_RUN=1 is set" {
|
|
163
|
+
$env:BEEOS_INSTALL_DRY_RUN = "1"
|
|
164
|
+
# Dot-source so we exercise the param-block + helper definitions
|
|
165
|
+
# without invoking the main install body. The dry-run hook
|
|
166
|
+
# `return`s before any state-changing call.
|
|
167
|
+
{ . $scriptPath } | Should -Not -Throw
|
|
168
|
+
}
|
|
169
|
+
}
|
package/scripts/install.ps1
CHANGED
|
@@ -65,6 +65,41 @@ param(
|
|
|
65
65
|
$ErrorActionPreference = "Stop"
|
|
66
66
|
$MinNodeVersion = 18
|
|
67
67
|
|
|
68
|
+
# ── PSCore + non-Windows guard (P2-8 of the install-link review) ─────
|
|
69
|
+
#
|
|
70
|
+
# The script header asks for `#Requires -Version 5.1`, but that only
|
|
71
|
+
# rejects Windows PowerShell 2.0/3.0/4.0 hosts — it does NOT block
|
|
72
|
+
# PowerShell Core (`pwsh`) running on macOS / Linux, where most of
|
|
73
|
+
# the cmdlets used below (`winget`, `choco`, `Start-Process -Verb
|
|
74
|
+
# RunAs`, `[Environment]::UserInteractive`) are either missing or
|
|
75
|
+
# behave very differently. Without an explicit check the user gets a
|
|
76
|
+
# stack of opaque errors midway through the script.
|
|
77
|
+
#
|
|
78
|
+
# The check happens BEFORE any helper is even defined so that the
|
|
79
|
+
# message is the first thing the user sees.
|
|
80
|
+
$script:IsWindowsHost = $true
|
|
81
|
+
if ($PSVersionTable.PSEdition -eq "Core") {
|
|
82
|
+
# PowerShell 6+ exposes the cross-platform `$IsWindows` automatic
|
|
83
|
+
# variable. Fall back to environment sniffing if some exotic host
|
|
84
|
+
# somehow strips it (e.g. an embedded runner without the standard
|
|
85
|
+
# automatic-variable provider — extremely rare but cheap to guard).
|
|
86
|
+
$autoIsWindows = Get-Variable -Name "IsWindows" -ErrorAction SilentlyContinue
|
|
87
|
+
if ($autoIsWindows) {
|
|
88
|
+
$script:IsWindowsHost = [bool]$autoIsWindows.Value
|
|
89
|
+
} else {
|
|
90
|
+
$script:IsWindowsHost = ($env:OS -eq "Windows_NT")
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (-not $script:IsWindowsHost) {
|
|
94
|
+
Write-Host ""
|
|
95
|
+
Write-Host " This installer is Windows-only." -ForegroundColor Red
|
|
96
|
+
Write-Host " On macOS or Linux, please use install.sh instead:" -ForegroundColor Yellow
|
|
97
|
+
Write-Host ""
|
|
98
|
+
Write-Host " curl -fsSL https://beeos.ai/install.sh | bash"
|
|
99
|
+
Write-Host ""
|
|
100
|
+
exit 2
|
|
101
|
+
}
|
|
102
|
+
|
|
68
103
|
$CliPackage = if ($Version -eq "latest") { "@beeos-ai/cli@latest" } else { "@beeos-ai/cli@$Version" }
|
|
69
104
|
|
|
70
105
|
# Sibling packages installed alongside the CLI so that `beeos device attach`
|
|
@@ -87,6 +122,22 @@ if (-not $env:BEEOS_API_URL -or $env:BEEOS_API_URL.Length -eq 0) {
|
|
|
87
122
|
$script:BeeosApiUrl = $env:BEEOS_API_URL
|
|
88
123
|
}
|
|
89
124
|
|
|
125
|
+
# P1-I of the install-link review: dump winget/choco stderr to a
|
|
126
|
+
# timestamped log so a Windows install failure leaves a forensic
|
|
127
|
+
# breadcrumb for support. Mirrors `install.sh`'s `BEEOS_INSTALL_LOG`.
|
|
128
|
+
# Honour `$env:BEEOS_INSTALL_LOG` if the caller supplied one (CI may
|
|
129
|
+
# want a deterministic path); otherwise default to a file under
|
|
130
|
+
# `$env:TEMP` named `beeos-install-<timestamp>.log`. The directory
|
|
131
|
+
# must exist before we redirect into it — `$env:TEMP` is guaranteed
|
|
132
|
+
# on Windows, so no `New-Item` ceremony.
|
|
133
|
+
if ($env:BEEOS_INSTALL_LOG -and $env:BEEOS_INSTALL_LOG.Length -gt 0) {
|
|
134
|
+
$script:BeeosInstallLog = $env:BEEOS_INSTALL_LOG
|
|
135
|
+
} else {
|
|
136
|
+
$logDir = if ($env:TEMP) { $env:TEMP } else { (Get-Location).Path }
|
|
137
|
+
$stamp = Get-Date -Format "yyyyMMdd-HHmmss"
|
|
138
|
+
$script:BeeosInstallLog = Join-Path $logDir ("beeos-install-{0}.log" -f $stamp)
|
|
139
|
+
}
|
|
140
|
+
|
|
90
141
|
# ── Helpers ──────────────────────────────────────────────────
|
|
91
142
|
|
|
92
143
|
function Write-BeeInfo { param([string]$Msg) Write-Host "[beeos] $Msg" -ForegroundColor Cyan }
|
|
@@ -97,8 +148,10 @@ function Write-BeeOk { param([string]$Msg) Write-Host "[beeos] $Msg" -Foregro
|
|
|
97
148
|
# ── Script-level telemetry (mirror of install.sh send_telemetry) ──
|
|
98
149
|
#
|
|
99
150
|
# Fire-and-forget POST to `/api/v1/telemetry/install`, capped at 2 s,
|
|
100
|
-
# error-swallowing.
|
|
101
|
-
#
|
|
151
|
+
# error-swallowing. Event names + payload shape mirror
|
|
152
|
+
# `web/packages/cli/src/telemetry.ts` (the Node-side source of truth);
|
|
153
|
+
# add an event in BOTH places or the Windows install will silently
|
|
154
|
+
# fall off the dashboard.
|
|
102
155
|
#
|
|
103
156
|
# Opt-out: `$env:BEEOS_NO_TELEMETRY = "1"`.
|
|
104
157
|
function Send-Telemetry {
|
|
@@ -203,15 +256,115 @@ function Test-NodeVersion {
|
|
|
203
256
|
|
|
204
257
|
# ── Auto-install Node.js ─────────────────────────────────────
|
|
205
258
|
|
|
259
|
+
# Detect whether the current PowerShell session is running with
|
|
260
|
+
# administrator privileges. winget / choco both need elevation to
|
|
261
|
+
# install machine-wide packages; without this check the failure
|
|
262
|
+
# manifests as opaque "access denied" errors deep inside the package
|
|
263
|
+
# manager output.
|
|
264
|
+
function Test-IsAdministrator {
|
|
265
|
+
try {
|
|
266
|
+
$identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
|
|
267
|
+
$principal = New-Object System.Security.Principal.WindowsPrincipal($identity)
|
|
268
|
+
return $principal.IsInRole(
|
|
269
|
+
[System.Security.Principal.WindowsBuiltInRole]::Administrator
|
|
270
|
+
)
|
|
271
|
+
} catch {
|
|
272
|
+
return $false
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
# Re-exec the script in an elevated PowerShell window when the
|
|
277
|
+
# current process is not an administrator. The user sees a UAC
|
|
278
|
+
# prompt; on consent, a fresh `pwsh.exe` / `powershell.exe` runs the
|
|
279
|
+
# same script with the same parameters, and the caller exits cleanly.
|
|
280
|
+
#
|
|
281
|
+
# Returns:
|
|
282
|
+
# $true — elevation requested; caller MUST exit immediately.
|
|
283
|
+
# $false — already elevated, or elevation declined / unavailable.
|
|
284
|
+
# Caller continues in the original (non-elevated) session.
|
|
285
|
+
function Request-Elevation {
|
|
286
|
+
if (Test-IsAdministrator) { return $false }
|
|
287
|
+
if ($env:BEEOS_NO_AUTO_ELEVATE -eq "1") {
|
|
288
|
+
Write-BeeWarn "BEEOS_NO_AUTO_ELEVATE=1 — skipping auto-elevation."
|
|
289
|
+
return $false
|
|
290
|
+
}
|
|
291
|
+
if (-not [Environment]::UserInteractive) {
|
|
292
|
+
# Non-interactive shells (CI, piped `irm | iex` without a
|
|
293
|
+
# console) cannot show a UAC prompt — re-execing would just
|
|
294
|
+
# spawn a window the user never sees. Bail to the caller's
|
|
295
|
+
# manual-fix branch instead.
|
|
296
|
+
Write-BeeWarn "Non-interactive shell — cannot prompt for elevation."
|
|
297
|
+
return $false
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
Write-BeeInfo "Requesting administrator privileges (UAC prompt) ..."
|
|
301
|
+
$self = $PSCommandPath
|
|
302
|
+
if (-not $self) {
|
|
303
|
+
# `irm | iex` produces no script path; we can't re-exec a
|
|
304
|
+
# one-liner, so let the caller print manual instructions.
|
|
305
|
+
Write-BeeWarn "Auto-elevation unavailable when piping the script (irm | iex)."
|
|
306
|
+
Write-BeeWarn "Please open PowerShell as Administrator and re-run the installer."
|
|
307
|
+
return $false
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
$pwsh = (Get-Command pwsh -ErrorAction SilentlyContinue).Source
|
|
311
|
+
if (-not $pwsh) {
|
|
312
|
+
$pwsh = (Get-Command powershell -ErrorAction SilentlyContinue).Source
|
|
313
|
+
}
|
|
314
|
+
if (-not $pwsh) {
|
|
315
|
+
Write-BeeWarn "Could not locate pwsh.exe / powershell.exe for elevation."
|
|
316
|
+
return $false
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
$argList = @(
|
|
320
|
+
"-NoProfile",
|
|
321
|
+
"-ExecutionPolicy", "Bypass",
|
|
322
|
+
"-File", $self
|
|
323
|
+
)
|
|
324
|
+
if ($Version -ne "latest") { $argList += @("-Version", $Version) }
|
|
325
|
+
if ($Device) { $argList += "-Device" }
|
|
326
|
+
if ($Passthrough) { $argList += $Passthrough }
|
|
327
|
+
|
|
328
|
+
try {
|
|
329
|
+
Start-Process -FilePath $pwsh -ArgumentList $argList -Verb RunAs | Out-Null
|
|
330
|
+
Write-BeeOk "Elevated installer launched in a new window."
|
|
331
|
+
return $true
|
|
332
|
+
} catch {
|
|
333
|
+
Write-BeeWarn "User declined elevation or UAC failed: $_"
|
|
334
|
+
return $false
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
206
338
|
function Install-NodeAuto {
|
|
207
339
|
Write-BeeInfo "Node.js not found. Attempting automatic installation..."
|
|
208
340
|
Write-Host ""
|
|
209
341
|
|
|
342
|
+
# Both winget (machine scope) and Chocolatey require admin rights
|
|
343
|
+
# to write into Program Files and HKLM. Try to escalate once; if
|
|
344
|
+
# the user accepts UAC the elevated script does the install and
|
|
345
|
+
# the original (non-elevated) session exits. If they decline we
|
|
346
|
+
# still try the package managers — winget's user-scope mode and
|
|
347
|
+
# choco's portable installs work without admin in some cases.
|
|
348
|
+
if (-not (Test-IsAdministrator)) {
|
|
349
|
+
if (Request-Elevation) {
|
|
350
|
+
# Elevated process took over; exit cleanly so the user
|
|
351
|
+
# doesn't see two installs racing.
|
|
352
|
+
exit 0
|
|
353
|
+
}
|
|
354
|
+
Write-BeeWarn "Continuing without admin — winget/choco may fail with EACCES."
|
|
355
|
+
}
|
|
356
|
+
|
|
210
357
|
$winget = Get-Command winget -ErrorAction SilentlyContinue
|
|
211
358
|
if ($winget) {
|
|
212
359
|
Write-BeeInfo "Installing Node.js LTS via winget..."
|
|
213
360
|
try {
|
|
214
|
-
|
|
361
|
+
# P1-I: route stderr to $BeeosInstallLog instead of $null
|
|
362
|
+
# so a failed winget run leaves a breadcrumb. The append
|
|
363
|
+
# operator (`2>>`) is intentional — we want every line of
|
|
364
|
+
# output across multiple `Install-NodeAuto` invocations
|
|
365
|
+
# within a single script run to land in the same file.
|
|
366
|
+
"[$(Get-Date -Format 'o')] winget install OpenJS.NodeJS.LTS" | Out-File -FilePath $BeeosInstallLog -Append -Encoding utf8
|
|
367
|
+
winget install OpenJS.NodeJS.LTS --accept-package-agreements --accept-source-agreements --silent 2>>$BeeosInstallLog
|
|
215
368
|
$env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" +
|
|
216
369
|
[System.Environment]::GetEnvironmentVariable("Path", "User")
|
|
217
370
|
if (Test-NodeVersion) {
|
|
@@ -220,6 +373,7 @@ function Install-NodeAuto {
|
|
|
220
373
|
}
|
|
221
374
|
} catch {
|
|
222
375
|
Write-BeeWarn "winget install failed: $_"
|
|
376
|
+
Write-BeeWarn " see log for details: $BeeosInstallLog"
|
|
223
377
|
}
|
|
224
378
|
}
|
|
225
379
|
|
|
@@ -227,7 +381,8 @@ function Install-NodeAuto {
|
|
|
227
381
|
if ($choco) {
|
|
228
382
|
Write-BeeInfo "Installing Node.js LTS via Chocolatey..."
|
|
229
383
|
try {
|
|
230
|
-
choco install nodejs-lts -
|
|
384
|
+
"[$(Get-Date -Format 'o')] choco install nodejs-lts" | Out-File -FilePath $BeeosInstallLog -Append -Encoding utf8
|
|
385
|
+
choco install nodejs-lts -y 2>>$BeeosInstallLog
|
|
231
386
|
$env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" +
|
|
232
387
|
[System.Environment]::GetEnvironmentVariable("Path", "User")
|
|
233
388
|
if (Test-NodeVersion) {
|
|
@@ -236,6 +391,7 @@ function Install-NodeAuto {
|
|
|
236
391
|
}
|
|
237
392
|
} catch {
|
|
238
393
|
Write-BeeWarn "Chocolatey install failed: $_"
|
|
394
|
+
Write-BeeWarn " see log for details: $BeeosInstallLog"
|
|
239
395
|
}
|
|
240
396
|
}
|
|
241
397
|
|
|
@@ -261,6 +417,13 @@ function Show-InstallHints {
|
|
|
261
417
|
Write-BeeInfo "Then run this script again:"
|
|
262
418
|
Write-Host " irm https://beeos.ai/install.ps1 | iex"
|
|
263
419
|
Write-Host ""
|
|
420
|
+
# P1-I: surface the install log so support requests include the
|
|
421
|
+
# exact winget / choco stderr that explains the failure.
|
|
422
|
+
if (Test-Path $BeeosInstallLog) {
|
|
423
|
+
Write-Host " Installer log:" -ForegroundColor White
|
|
424
|
+
Write-Host " $BeeosInstallLog"
|
|
425
|
+
Write-Host ""
|
|
426
|
+
}
|
|
264
427
|
}
|
|
265
428
|
|
|
266
429
|
# ── Existing install detection ───────────────────────────────
|
|
@@ -277,17 +440,32 @@ function Get-BeeosVersion {
|
|
|
277
440
|
}
|
|
278
441
|
|
|
279
442
|
function Read-ExistingInstallAction {
|
|
443
|
+
# Note on naming: "Reinstall + upgrade CLI" deliberately differs
|
|
444
|
+
# from the `beeos init` menu's "Upgrade CLI & agents" item. The
|
|
445
|
+
# script-side action also re-bootstraps Node / pnpm and re-applies
|
|
446
|
+
# this shell's env vars before exec'ing the new binary, which the
|
|
447
|
+
# in-CLI upgrade does NOT do — choosing a different verb keeps the
|
|
448
|
+
# two flows distinguishable. See `install.sh` for the matching
|
|
449
|
+
# docstring.
|
|
450
|
+
#
|
|
451
|
+
# P1-E of the install-link review: the labels rendered below are
|
|
452
|
+
# anchored against `install.sh::prompt_existing_install_action` +
|
|
453
|
+
# `init.ts::decideAction` via
|
|
454
|
+
# `web/packages/cli/scripts/_existing_install_actions.json` and
|
|
455
|
+
# the vitest grep lint at
|
|
456
|
+
# `web/packages/cli/src/__tests__/existing-install-actions.test.ts`.
|
|
457
|
+
# Keep at least one keyword from `keywords[action]` per option.
|
|
280
458
|
$ver = Get-BeeosVersion
|
|
281
459
|
if (-not $ver) { $ver = "unknown" }
|
|
282
460
|
Write-BeeWarn "BeeOS CLI is already installed (v$ver)."
|
|
283
461
|
Write-Host ""
|
|
284
|
-
Write-Host " [1]
|
|
285
|
-
Write-Host " [2] Re-run beeos init without
|
|
462
|
+
Write-Host " [1] Reinstall + upgrade CLI to the latest version, then run beeos init"
|
|
463
|
+
Write-Host " [2] Re-run beeos init without reinstalling"
|
|
286
464
|
Write-Host " [3] Skip (do nothing)"
|
|
287
465
|
Write-Host ""
|
|
288
466
|
|
|
289
467
|
if (-not [Environment]::UserInteractive) {
|
|
290
|
-
Write-BeeInfo "Non-interactive shell — defaulting to
|
|
468
|
+
Write-BeeInfo "Non-interactive shell — defaulting to reinstall."
|
|
291
469
|
return "upgrade"
|
|
292
470
|
}
|
|
293
471
|
|
|
@@ -367,6 +545,12 @@ function Invoke-BeeosCli {
|
|
|
367
545
|
Write-BeeInfo "Installing $CliPackage globally..."
|
|
368
546
|
& npm install -g $CliPackage
|
|
369
547
|
if ($LASTEXITCODE -ne 0) {
|
|
548
|
+
# P0-A of the install-link review: fail-after-bootstrap signal.
|
|
549
|
+
# The `install.bootstrap.cli_installed` event is only emitted
|
|
550
|
+
# AFTER npm + the device-agent suite report success, so npm
|
|
551
|
+
# failures are recorded as a distinct outcome (not as silent
|
|
552
|
+
# post-success).
|
|
553
|
+
Send-Telemetry -Event "install.bootstrap.npm_fail" -ErrorCode "npm_install_cli_failed" -Success $false
|
|
370
554
|
Write-BeeError "npm install -g $CliPackage failed."
|
|
371
555
|
Write-BeeError ""
|
|
372
556
|
Write-BeeError "Common fixes:"
|
|
@@ -376,11 +560,63 @@ function Invoke-BeeosCli {
|
|
|
376
560
|
exit 1
|
|
377
561
|
}
|
|
378
562
|
Install-DeviceAgentSuite
|
|
563
|
+
Send-Telemetry -Event "install.bootstrap.cli_installed"
|
|
564
|
+
|
|
565
|
+
# P1-J of the install-link review: when the user invokes the
|
|
566
|
+
# installer via `irm https://beeos.ai/install.ps1 | iex` the
|
|
567
|
+
# script's stdin is the pipe from `Invoke-RestMethod`, not the
|
|
568
|
+
# console. If we then `& beeos @args`, `beeos init`'s prompts read
|
|
569
|
+
# from that exhausted pipe and silently take their default values
|
|
570
|
+
# — exactly what install.sh's `</dev/tty` reattachment was added
|
|
571
|
+
# to prevent on POSIX. We mirror that behaviour here:
|
|
572
|
+
# `Start-Process -NoNewWindow -Wait` reuses the parent's window
|
|
573
|
+
# but gives the child a fresh stdin handle attached to the
|
|
574
|
+
# console, so prompts work as the user expects.
|
|
575
|
+
$stdinRedirected = $false
|
|
576
|
+
try {
|
|
577
|
+
$stdinRedirected = [Console]::IsInputRedirected
|
|
578
|
+
} catch {
|
|
579
|
+
# Older / restricted hosts (e.g. ConstrainedLanguage mode)
|
|
580
|
+
# lose access to [Console]; fall through to the legacy `&`
|
|
581
|
+
# path rather than blowing up here.
|
|
582
|
+
$stdinRedirected = $false
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
if ($stdinRedirected -and [Environment]::UserInteractive) {
|
|
586
|
+
Write-BeeInfo "Detected piped stdin — re-attaching console for interactive prompts."
|
|
587
|
+
$beeosCmd = Get-Command beeos -ErrorAction SilentlyContinue
|
|
588
|
+
if ($beeosCmd) {
|
|
589
|
+
# `-WorkingDirectory` keeps cwd predictable; without it
|
|
590
|
+
# `Start-Process` defaults to the user's home directory in
|
|
591
|
+
# some hosts, which surprises later steps.
|
|
592
|
+
Start-Process -FilePath $beeosCmd.Source `
|
|
593
|
+
-ArgumentList $args `
|
|
594
|
+
-NoNewWindow `
|
|
595
|
+
-Wait `
|
|
596
|
+
-WorkingDirectory (Get-Location).Path
|
|
597
|
+
return
|
|
598
|
+
}
|
|
599
|
+
# If `beeos` somehow isn't on PATH yet (race with PATH refresh
|
|
600
|
+
# under a brand-new install), fall through to `&` which will
|
|
601
|
+
# fail loudly — better than silently consuming a piped EOF.
|
|
602
|
+
}
|
|
603
|
+
|
|
379
604
|
& beeos @args
|
|
380
605
|
}
|
|
381
606
|
|
|
382
607
|
# ── Main ─────────────────────────────────────────────────────
|
|
383
608
|
|
|
609
|
+
# Dry-run hook for the Pester smoke test (P2-2 of the install-link
|
|
610
|
+
# review). When `BEEOS_INSTALL_DRY_RUN=1` is set, exit BEFORE running
|
|
611
|
+
# any state-changing operation but AFTER all helper functions and
|
|
612
|
+
# `param`-block validation have completed. This lets CI assert
|
|
613
|
+
# "the script parses + every helper is callable" without spawning a
|
|
614
|
+
# real Node install or hitting the telemetry endpoint.
|
|
615
|
+
if ($env:BEEOS_INSTALL_DRY_RUN -eq "1") {
|
|
616
|
+
Write-Host "[beeos] BEEOS_INSTALL_DRY_RUN=1 — exiting early (smoke test mode)" -ForegroundColor Yellow
|
|
617
|
+
return
|
|
618
|
+
}
|
|
619
|
+
|
|
384
620
|
Write-Host ""
|
|
385
621
|
Write-Host " BeeOS CLI Installer" -ForegroundColor White
|
|
386
622
|
Write-Host " https://beeos.ai" -ForegroundColor DarkGray
|
|
@@ -414,9 +650,10 @@ if ($beeosCmd) {
|
|
|
414
650
|
exit 0
|
|
415
651
|
}
|
|
416
652
|
"upgrade" {
|
|
417
|
-
Write-BeeInfo "
|
|
653
|
+
Write-BeeInfo "Reinstalling $CliPackage..."
|
|
418
654
|
& npm install -g $CliPackage
|
|
419
655
|
if ($LASTEXITCODE -ne 0) {
|
|
656
|
+
Send-Telemetry -Event "install.bootstrap.npm_fail" -ErrorCode "npm_install_cli_failed_upgrade" -Success $false
|
|
420
657
|
Write-BeeError "npm install -g $CliPackage failed."
|
|
421
658
|
Write-BeeError ""
|
|
422
659
|
Write-BeeError "Common fixes:"
|
|
@@ -436,7 +673,13 @@ if ($beeosCmd) {
|
|
|
436
673
|
Write-BeeInfo "Running BeeOS CLI..."
|
|
437
674
|
Write-Host ""
|
|
438
675
|
|
|
439
|
-
|
|
676
|
+
# P0-A of the install-link review: this event marks "Node bootstrap
|
|
677
|
+
# finished, about to dispatch into npm install + the CLI". The
|
|
678
|
+
# follow-up `install.bootstrap.cli_installed` (fired inside
|
|
679
|
+
# Invoke-BeeosCli after npm succeeds) is the real "user has working
|
|
680
|
+
# `beeos` on PATH" signal. The two are split so dashboards can tell
|
|
681
|
+
# bootstrap failures apart from npm/CLI failures.
|
|
682
|
+
Send-Telemetry -Event "install.bootstrap.bootstrap_done"
|
|
440
683
|
|
|
441
684
|
if ($Device) {
|
|
442
685
|
Invoke-BeeosCli -Subcommand "device" -ExtraArgs (@("attach") + $Passthrough)
|