Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 72 additions & 10 deletions scripts/install-atomgit.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,15 @@ $AtomgitApi = "https://api.atomgit.com/api/v5/repos/wiseflow/xiaobei"
$Root = if ($env:XIAOBEI_HOME) { $env:XIAOBEI_HOME } else { Join-Path $env:USERPROFILE "xiaobei" }
$OpenclawHome = if ($env:OPENCLAW_HOME) { $env:OPENCLAW_HOME } else { Join-Path $env:USERPROFILE ".openclaw" }

# Behavior switches (env vars, =1 to enable)
$Force = ($env:XIAOBEI_FORCE -eq "1" -or $env:XIAOBEI_FORCE -eq "true")
$SkipBind = ($env:XIAOBEI_SKIP_BIND -eq "1" -or $env:XIAOBEI_SKIP_BIND -eq "true")
$SkipBrowser = ($env:XIAOBEI_SKIP_BROWSER -eq "1" -or $env:XIAOBEI_SKIP_BROWSER -eq "true")
$NoPrompt = ($env:XIAOBEI_NO_PROMPT -eq "1" -or $env:XIAOBEI_NO_PROMPT -eq "true")
# Behavior switches (env vars, =1 to enable).
# IMPORTANT: use $script: scope so these are visible inside functions (Place-Config,
# Main, etc.). A bare $Force at the top level creates a *script-scope* variable that
# functions CANNOT see - they'd read a local $null instead, so XIAOBEI_FORCE=1 etc.
# would silently never take effect (a real bug observed in testing).
$script:Force = ($env:XIAOBEI_FORCE -eq "1" -or $env:XIAOBEI_FORCE -eq "true")
$script:SkipBind = ($env:XIAOBEI_SKIP_BIND -eq "1" -or $env:XIAOBEI_SKIP_BIND -eq "true")
$script:SkipBrowser = ($env:XIAOBEI_SKIP_BROWSER -eq "1" -or $env:XIAOBEI_SKIP_BROWSER -eq "true")
$script:NoPrompt = ($env:XIAOBEI_NO_PROMPT -eq "1" -or $env:XIAOBEI_NO_PROMPT -eq "true")

$NodeExe = Join-Path $Root "tools\node\node.exe"
$NpmCmd = Join-Path $Root "tools\node\npm.cmd"
Expand Down Expand Up @@ -216,7 +220,7 @@ function Place-Config {

$needPlace = $false; $reason = ""
if (-not (Test-Path $cfg)) { $needPlace = $true; $reason = "not present" }
elseif ($Force) { $needPlace = $true; $reason = "XIAOBEI_FORCE=1" }
elseif ($script:Force) { $needPlace = $true; $reason = "XIAOBEI_FORCE=1" }
else {
try {
# Must explicitly read as UTF-8: openclaw writes its config as UTF-8 without BOM, while
Expand Down Expand Up @@ -318,9 +322,66 @@ function Run-SetupCrew {
else { Write-Ok "crew templates set up" }
}

# --- 7b. Expose skill wrappers as .cmd shims into ~\.openclaw\bin ---
# Mirrors the .sh route's expose_skill_wrappers + ensure_openclaw_bin_in_path
# (scripts/lib/skill-wrappers.sh). On Windows we cannot rely on symlinks
# (need admin/Developer Mode), so we generate a <skill>.cmd shim per skill
# that forwards to the bundled bash + the skill's <skill>.sh wrapper.
# The shim lives in ~\.openclaw\bin so the agent can call `<skill> <cmd>`
# from anywhere on PATH.
function Expose-SkillWrappers {
$binDir = Join-Path $OpenclawHome "bin"
New-Item -ItemType Directory -Force -Path $binDir | Out-Null

# The bundled bash shipped in the tarball (Git Bash fallback handled below).
$bashExe = $false
$bashCandidates = @(
(Join-Path $Root "tools\git\bin\bash.exe"),
"C:\Program Files\Git\bin\bash.exe",
"C:\Program Files\Git\usr\bin\bash.exe"
)
foreach ($c in $bashCandidates) { if (Test-Path $c) { $bashExe = $c; break } }
if (-not $bashExe) { $bashExe = (Get-Command bash.exe -ErrorAction SilentlyContinue).Source }
if (-not $bashExe) {
Write-Warn "no bash found; cannot expose skill wrappers as .cmd shims"
return
}

$exposed = 0
foreach ($skillsRoot in @((Join-Path $Root "skills"), (Join-Path $Root "crews\main\skills"))) {
if (-not (Test-Path $skillsRoot)) { continue }
Get-ChildItem -Path $skillsRoot -Directory | ForEach-Object {
$skillName = $_.Name
if ($skillName.StartsWith("_")) { return } # skip _shared etc
$wrapper = Join-Path $_.FullName "$skillName.sh"
if (-not (Test-Path $wrapper)) { return }
$shim = Join-Path $binDir "$skillName.cmd"
$wrapperFwd = ($wrapper -replace '\\', '/')
$bashFwd = ($bashExe -replace '\\', '/')
$body = "@echo off`r`n`"$bashFwd`" `"$wrapperFwd`" %*`r`n"
$enc = New-Object System.Text.UTF8Encoding($false)
[System.IO.File]::WriteAllText($shim, $body, $enc)
$exposed++
}
}

if ($exposed -gt 0) {
Write-Ok "exposed $exposed skill wrapper(s) -> $binDir"
# Inject ~\.openclaw\bin into user PATH (idempotent, safe non-truncating way)
$userPath = [Environment]::GetEnvironmentVariable("PATH", "User")
if ($userPath -notlike "*$binDir*") {
$newPath = "$binDir;$userPath"
[Environment]::SetEnvironmentVariable("PATH", $newPath, "User")
Write-Ok "added $binDir to user PATH (takes effect in a new terminal)"
}
} else {
Write-Host " [i] no skill wrappers found to expose" -ForegroundColor Yellow
}
}

# --- 8. camoufox-cli ---
function Install-CamoufoxCli {
if ($SkipBrowser) {
if ($script:SkipBrowser) {
Write-Host " [i] skipping camoufox-cli browser binary (XIAOBEI_SKIP_BROWSER=1); run manually later: camoufox-cli install" -ForegroundColor Yellow
return
}
Expand Down Expand Up @@ -475,7 +536,7 @@ function Install-GatewayAndEnv {

# --- Interactive AWK_API_KEY prompt ---
$awkKey = $env:AWK_API_KEY
if (-not $NoPrompt -and -not $awkKey) {
if (-not $script:NoPrompt -and -not $awkKey) {
$awkKey = Read-Host "Enter AWK_API_KEY (Volces ARK API key)"
}

Expand Down Expand Up @@ -549,7 +610,7 @@ function Test-WeixinBound {
}

function Bind-WeixinChannel {
if ($SkipBind -or $NoPrompt) {
if ($script:SkipBind -or $script:NoPrompt) {
Write-Host " [i] skipping WeChat scan-to-bind (XIAOBEI_SKIP_BIND / XIAOBEI_NO_PROMPT=1); run manually later: openclaw channels login --channel openclaw-weixin" -ForegroundColor Yellow
return
}
Expand All @@ -576,7 +637,7 @@ function Main {

# Detect whether already installed (decides update vs fresh install)
$cfgExisting = Join-Path $OpenclawHome "openclaw.json"
$isUpdate = (Test-Path $cfgExisting) -and -not $Force
$isUpdate = (Test-Path $cfgExisting) -and -not $script:Force
if ($isUpdate) {
Write-Warn "detected existing install ($cfgExisting) -> taking the update route, preserving runtime data (XIAOBEI_FORCE=1 to force overwrite)"
# Stop the gateway first (mirrors install.sh stop_gateway_if_running): otherwise tar
Expand Down Expand Up @@ -624,6 +685,7 @@ function Main {
} else {
Place-Config
Run-SetupCrew
Expose-SkillWrappers
Install-GatewayAndEnv
Bind-WeixinChannel
}
Expand Down
49 changes: 10 additions & 39 deletions scripts/install-atomgit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -473,44 +473,17 @@ install_python_deps() {
ui_warn "$PY_BIN -m pip 不可用,跳过 python 依赖"
return 0
fi
ui_info "Installing python skill deps via $PY_BIN (--user)"
ui_info "Installing python skill deps via $PY_BIN (--user --break-system-packages)"
# --no-warn-script-location 抑制 'script installed in .../bin which is not on PATH'(小白噪音)
# --disable-pip-version-check 抑制 pip 升级提示
# PEP 668 兜底:Homebrew/Debian 等 externally-managed 环境下,pip install --user 仍被拒,
# 需加 --break-system-packages 才能写用户目录(--user 限目标,不碰系统 site-packages,安全)。
local pep668_flag=""
local pyver; pyver="$("$PY_BIN" -c 'import sys;print("%d.%d"%sys.version_info[:2])' 2>/dev/null || echo "")"
if [ -n "$pyver" ]; then
local inc_dir
inc_dir="$("$PY_BIN" -c 'import sysconfig;print(sysconfig.get_path("include"))' 2>/dev/null || echo "")"
local sys_prefix
sys_prefix="$("$PY_BIN" -c 'import sys;print(sys.prefix)' 2>/dev/null || echo "")"
# EXTERNALLY-MANAGED 标记文件在 stdlib 目录(python3.x/)下
if [ -n "$inc_dir" ] && [ -f "$(dirname "$inc_dir")/EXTERNALLY-MANAGED" ]; then
pep668_flag="--break-system-packages"
ui_info "检测到 PEP 668 externally-managed 环境,加 --break-system-packages"
fi
fi
# 兜底:上面探测失败时,pip 报 externally-managed-environment 退出码 1,重试加 flag
local pip_args=(--user --no-warn-script-location --disable-pip-version-check
-i https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com)
[ -n "$pep668_flag" ] && pip_args+=("$pep668_flag")
if ! printf '%s' "$merged" | sort -u | grep -vE '^\s*#|^\s*$' | \
"$PY_BIN" -m pip install "${pip_args[@]}" -r /dev/stdin; then
# 首次没加 flag 且报 externally-managed,重试加 flag
if [ -z "$pep668_flag" ]; then
ui_warn "pip install 失败,尝试加 --break-system-packages 重试..."
if printf '%s' "$merged" | sort -u | grep -vE '^\s*#|^\s*$' | \
"$PY_BIN" -m pip install --user --break-system-packages \
--no-warn-script-location --disable-pip-version-check \
-i https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com \
-r /dev/stdin; then
ui_success "Python deps done (with --break-system-packages)"
return 0
fi
fi
# PEP 668:现代系统(Homebrew/Debian/PEP 668)下 pip install --user 仍被拒,
# 直接加 --break-system-packages 跨过;--user 限目标目录,不碰系统 site-packages,安全。
printf '%s' "$merged" | sort -u | grep -vE '^\s*#|^\s*$' | \
"$PY_BIN" -m pip install --user --break-system-packages \
--no-warn-script-location --disable-pip-version-check \
-i https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com \
-r /dev/stdin || \
ui_warn "pip install 部分失败,可稍后手动补"
fi
ui_success "Python deps done"
}

Expand Down Expand Up @@ -1400,12 +1373,10 @@ install_gateway_and_env() {
# 引擎 resolveStateDir 把 OPENCLAW_HOME 当 homedir 再 append /.openclaw(见
# openclaw/src/config/paths.ts),故若用户 shell export 了 OPENCLAW_HOME=~/.openclaw,
# 会产出嵌套路径。本脚本顶部已把 OPENCLAW_HOME 设成内部变量且未 export,不会传给引擎子进程,
# 所以这里只打 warning 提示用户清理 shell 环境,不能 unset——unset 会误伤脚本内部变量,
# 所以这里只打 info 提示用户清理 shell 环境变量,不能 unset——unset 会误伤脚本内部变量,
# 导致后续 ${OPENCLAW_HOME//...} 在 set -u 下报 unbound variable。
if [ -n "${OPENCLAW_HOME:-}" ] && [ "$(cd "${OPENCLAW_HOME}" && pwd)" = "$(cd "$HOME/.openclaw" && pwd)" ]; then
ui_warn "检测到 OPENCLAW_HOME=$OPENCLAW_HOME 已设成 state dir 路径"
ui_warn " 这会让引擎嵌套 append /.openclaw → ~/.openclaw/.openclaw/,config 全落错位置"
ui_warn " 正解:清掉 OPENCLAW_HOME,改用 OPENCLAW_STATE_DIR 显式指定 state dir"
ui_info "检测到 shell 里 export OPENCLAW_HOME=$OPENCLAW_HOME(不影响本次安装,但建议清掉避免引擎嵌套)"
fi

# redirect stdin from /dev/tty so interactive read 工作于 curl|bash
Expand Down
Loading