diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a2f16aad63..81362618158 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -154,6 +154,7 @@ Docs: https://docs.openclaw.ai ### Fixes - Docs/Docker: document a local Compose override for Docker Desktop DNS failures in the shared-network `openclaw-cli` sidecar, keeping the default compose setup hardened while unblocking `openclaw plugins install` when users opt in. Fixes #79018. Thanks @Jason-Vaughan. +- Installer: when npm installs `openclaw` outside the parent shell PATH, print follow-up commands with the resolved binary path instead of telling users to run `openclaw` from a shell that will report `command not found`. Fixes #72382. Thanks @jbob762. - Compute plugin callback authorization dynamically [AI]. (#78866) Thanks @pgondhi987. - fix(active-memory): require admin scope for global toggles [AI]. (#78863) Thanks @pgondhi987. - Honor owner enforcement for native commands [AI]. (#78864) Thanks @pgondhi987. diff --git a/scripts/install.sh b/scripts/install.sh index edaf1559d80..abb62dab53b 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -2006,6 +2006,24 @@ warn_shell_path_missing_dir() { echo " export PATH=\"${dir}:\$PATH\"" } +openclaw_command_for_user() { + local claw="${1:-}" + if [[ -z "$claw" ]]; then + echo "openclaw" + return 0 + fi + + local claw_dir="${claw%/*}" + if [[ "$claw_dir" != "$claw" ]] && path_has_dir "$ORIGINAL_PATH" "$claw_dir"; then + echo "openclaw" + return 0 + fi + + local quoted_claw="" + printf -v quoted_claw '%q' "$claw" + echo "$quoted_claw" +} + ensure_npm_global_bin_on_path() { local bin_dir="" bin_dir="$(npm_global_bin_dir || true)" @@ -2300,7 +2318,9 @@ run_bootstrap_onboarding_if_needed() { fi if [[ ! -r /dev/tty || ! -w /dev/tty ]]; then - ui_info "BOOTSTRAP.md found but no TTY; run openclaw onboard to finish setup" + local user_claw + user_claw="$(openclaw_command_for_user "${OPENCLAW_BIN:-}")" + ui_info "BOOTSTRAP.md found but no TTY; run ${user_claw} onboard to finish setup" return fi @@ -2316,7 +2336,9 @@ run_bootstrap_onboarding_if_needed() { fi "$claw" onboard || { - ui_error "Onboarding failed; run openclaw onboard to retry" + local user_claw + user_claw="$(openclaw_command_for_user "$claw")" + ui_error "Onboarding failed; run ${user_claw} onboard to retry" return } } @@ -2702,11 +2724,15 @@ main() { ui_warn "Doctor failed; skipping plugin updates" fi else - ui_info "No TTY; run openclaw doctor and openclaw plugins update --all manually" + local user_claw + user_claw="$(openclaw_command_for_user "${OPENCLAW_BIN:-}")" + ui_info "No TTY; run ${user_claw} doctor and ${user_claw} plugins update --all manually" fi else if [[ "$NO_ONBOARD" == "1" || "$skip_onboard" == "true" ]]; then - ui_info "Skipping onboard (requested); run openclaw onboard later" + local user_claw + user_claw="$(openclaw_command_for_user "${OPENCLAW_BIN:-}")" + ui_info "Skipping onboard (requested); run ${user_claw} onboard later" else local config_path="${OPENCLAW_CONFIG_PATH:-$HOME/.openclaw/openclaw.json}" if [[ -f "${config_path}" || -f "$HOME/.clawdbot/clawdbot.json" ]]; then @@ -2731,7 +2757,9 @@ main() { exec { expect(result?.stdout).toContain("using this user prefix"); expect(result?.stdout).not.toContain("has been saved"); }); + + it("uses a quoted absolute openclaw path in follow-up commands when npm bin is not on the original PATH", () => { + const tmp = mkdtempSync(join(tmpdir(), "openclaw-install-command-")); + const npmBin = join(tmp, "npm bin"); + const visibleBin = join(tmp, "visible-bin"); + mkdirSync(npmBin, { recursive: true }); + mkdirSync(visibleBin, { recursive: true }); + const openclawBin = join(npmBin, "openclaw"); + writeFileSync(openclawBin, "#!/bin/sh\nexit 0\n"); + chmodSync(openclawBin, 0o755); + + let result: ReturnType | undefined; + try { + result = runInstallShell(` + set -euo pipefail + source "${SCRIPT_PATH}" + ORIGINAL_PATH=${JSON.stringify(`${visibleBin}:/usr/bin:/bin`)} + printf 'missing=%s\\n' "$(openclaw_command_for_user "${openclawBin}")" + ORIGINAL_PATH=${JSON.stringify(`${npmBin}:${visibleBin}:/usr/bin:/bin`)} + printf 'present=%s\\n' "$(openclaw_command_for_user "${openclawBin}")" + `); + } finally { + rmSync(tmp, { recursive: true, force: true }); + } + + expect(result?.status).toBe(0); + expect(result?.stdout).toContain(`missing=${openclawBin.replace(/ /g, "\\ ")}`); + expect(result?.stdout).toContain("present=openclaw"); + }); }); describe("install.sh macOS Homebrew Node behavior", () => {