diff --git a/install.sh b/install.sh index 4d8a08ef1..98a8eeec7 100755 --- a/install.sh +++ b/install.sh @@ -165,6 +165,7 @@ download_broker_binary() { if curl -fsSL "$download_url" -o "$target_path" 2>/dev/null; then chmod +x "$target_path" + strip_quarantine "$target_path" # Verify binary works (Rust clap binary supports --help) if "$target_path" --help &>/dev/null; then success "Downloaded broker binary (workflow agent spawning)" @@ -217,6 +218,7 @@ download_dashboard_binary() { if gunzip -c "${temp_file}.gz" > "$target_path" 2>/dev/null; then rm -f "${temp_file}.gz" chmod +x "$target_path" + strip_quarantine "$target_path" if "$target_path" --version &>/dev/null; then success "Downloaded standalone dashboard-server binary" @@ -244,6 +246,7 @@ download_dashboard_binary() { if [ "$file_size" -gt 1000000 ]; then chmod +x "$target_path" + strip_quarantine "$target_path" if "$target_path" --version &>/dev/null; then success "Downloaded standalone dashboard-server binary" @@ -325,6 +328,13 @@ has_command() { command -v "$1" &> /dev/null } +# Strip macOS Gatekeeper quarantine attribute from a binary +strip_quarantine() { + if [ "$OS" = "darwin" ] && has_command xattr; then + xattr -d com.apple.quarantine "$1" 2>/dev/null || true + fi +} + # Download relay-acp binary for Zed editor integration download_relay_acp() { step "Downloading relay-acp binary (Zed editor integration)..." @@ -354,6 +364,7 @@ download_relay_acp() { if gunzip -c "${temp_file}.gz" > "$target_path" 2>/dev/null; then rm -f "${temp_file}.gz" chmod +x "$target_path" + strip_quarantine "$target_path" if "$target_path" --help &>/dev/null; then success "Downloaded relay-acp binary (Zed ACP bridge)" @@ -379,6 +390,7 @@ download_relay_acp() { if [ "$file_size" -gt 1000000 ]; then chmod +x "$target_path" + strip_quarantine "$target_path" if "$target_path" --help &>/dev/null; then success "Downloaded relay-acp binary (Zed ACP bridge)" @@ -465,6 +477,7 @@ download_standalone_binary() { if gunzip -c "${temp_file}.gz" > "$target_path" 2>/dev/null; then rm -f "${temp_file}.gz" chmod +x "$target_path" + strip_quarantine "$target_path" # Verify the binary works if "$target_path" --version &>/dev/null; then @@ -501,6 +514,7 @@ download_standalone_binary() { if [ "$file_size" -gt 1000000 ]; then chmod +x "$target_path" + strip_quarantine "$target_path" # Verify the binary works if "$target_path" --version &>/dev/null; then @@ -686,6 +700,18 @@ verify_installation() { if command -v agent-relay &> /dev/null; then local installed_version=$(agent-relay --version 2>/dev/null || echo "unknown") success "agent-relay $installed_version installed successfully!" + + # Warn if another version shadows the one we just installed + local which_path=$(command -v agent-relay) + if [ -x "$BIN_DIR/agent-relay" ] && [ "$which_path" != "$BIN_DIR/agent-relay" ]; then + local other_version=$("$BIN_DIR/agent-relay" --version 2>/dev/null || echo "unknown") + if [ "$installed_version" != "$other_version" ]; then + warn "Another agent-relay ($installed_version) at $which_path shadows the newly installed $other_version at $BIN_DIR/agent-relay" + echo " To fix, either:" + echo " 1. Uninstall the old version: npm uninstall -g agent-relay" + echo " 2. Or ensure $BIN_DIR is earlier in your PATH" + fi + fi elif [ -x "$BIN_DIR/agent-relay" ]; then local installed_version=$("$BIN_DIR/agent-relay" --version 2>/dev/null || echo "unknown") success "agent-relay $installed_version installed to $BIN_DIR" diff --git a/packages/sdk/src/client.ts b/packages/sdk/src/client.ts index 3ebb5532c..e369ba0a5 100644 --- a/packages/sdk/src/client.ts +++ b/packages/sdk/src/client.ts @@ -743,8 +743,10 @@ function getLatestVersionSync(): string | null { timeout: 15_000, stdio: ['pipe', 'pipe', 'pipe'], }).toString(); - const match = result.match(/"tag_name"\s*:\s*"v?([^"]+)"/); - return match?.[1] ?? null; + const match = result.match(/"tag_name"\s*:\s*"([^"]+)"/); + if (!match?.[1]) return null; + // Strip tag prefixes: "openclaw-v3.1.18" -> "3.1.18", "v3.1.18" -> "3.1.18" + return match[1].replace(/^openclaw-/, '').replace(/^v/, ''); } catch { return null; } @@ -784,8 +786,16 @@ function installBrokerBinary(): string { }); fs.chmodSync(targetPath, 0o755); - // macOS: re-sign to avoid Gatekeeper issues + // macOS: strip quarantine attribute and re-sign to avoid Gatekeeper issues if (process.platform === 'darwin') { + try { + execSync(`xattr -d com.apple.quarantine "${targetPath}" 2>/dev/null || true`, { + timeout: 10_000, + stdio: ['pipe', 'pipe', 'pipe'], + }); + } catch { + // Non-fatal + } try { execSync(`codesign --force --sign - "${targetPath}"`, { timeout: 10_000, diff --git a/src/cli/lib/core-maintenance.ts b/src/cli/lib/core-maintenance.ts index ee141509a..56372ad4b 100644 --- a/src/cli/lib/core-maintenance.ts +++ b/src/cli/lib/core-maintenance.ts @@ -268,6 +268,56 @@ export async function runUninstallCommand( removeZedConfig(serverName, deps.fs, isDryRun, deps.log); } + // --- Binary removal (standalone binaries + npm packages) --- + const homeDir = os.homedir(); + const standaloneBinDir = path.join(homeDir, '.local', 'bin'); + const installBinDir = path.join(homeDir, '.agent-relay', 'bin'); + + // Remove standalone binaries from ~/.local/bin + for (const binaryName of ['agent-relay', 'relay-dashboard-server', 'relay-acp']) { + const binPath = path.join(standaloneBinDir, binaryName); + if (deps.fs.existsSync(binPath)) { + if (isDryRun) { + deps.log(`[dry-run] Would remove binary: ${binPath}`); + } else { + try { + deps.fs.unlinkSync(binPath); + deps.log(`Removed ${binPath}`); + } catch { + // Best-effort. + } + } + } + } + + // Remove broker binary from ~/.agent-relay/bin/ (not the parent dir which stores global data) + if (deps.fs.existsSync(installBinDir)) { + if (isDryRun) { + deps.log(`[dry-run] Would remove directory: ${installBinDir}`); + } else { + try { + deps.fs.rmSync(installBinDir, { recursive: true, force: true }); + deps.log(`Removed ${installBinDir}`); + } catch { + // Best-effort. + } + } + } + + // Remove npm-installed packages + if (!isDryRun) { + for (const pkg of ['agent-relay', '@agent-relay/dashboard-server', '@agent-relay/acp-bridge']) { + try { + await deps.execCommand(`npm uninstall -g ${pkg}`); + deps.log(`Uninstalled npm package: ${pkg}`); + } catch { + // Package may not be installed via npm — that's fine. + } + } + } else { + deps.log('[dry-run] Would run: npm uninstall -g agent-relay @agent-relay/dashboard-server @agent-relay/acp-bridge'); + } + // --- Snippet cleanup (CLAUDE.md, GEMINI.md, AGENTS.md) --- if (options.snippets) { for (const fileName of SNIPPET_TARGET_FILES) {