fix: streamline embedded webui install flow

This commit is contained in:
lpf
2026-03-09 15:46:25 +08:00
parent cf6bff2709
commit 3b177cf296
4 changed files with 282 additions and 247 deletions

View File

@@ -181,7 +181,6 @@ package-all: build-all
tar -czf "$$archive" "$$bin"; \ tar -czf "$$archive" "$$bin"; \
fi; \ fi; \
done done
@tar -czf "$(BUILD_DIR)/webui.tar.gz" -C "$(DEV_WEBUI_DIR)" dist
@set -e; cd $(BUILD_DIR); \ @set -e; cd $(BUILD_DIR); \
if command -v sha256sum >/dev/null 2>&1; then \ if command -v sha256sum >/dev/null 2>&1; then \
sha256sum *.tar.gz *.zip 2>/dev/null | tee checksums.txt || true; \ sha256sum *.tar.gz *.zip 2>/dev/null | tee checksums.txt || true; \

View File

@@ -5,11 +5,25 @@ import (
"io/fs" "io/fs"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"clawgo/pkg/config" "clawgo/pkg/config"
) )
type onboardOptions struct {
syncWebUIOnly bool
}
func onboard() { func onboard() {
opts := parseOnboardOptions(os.Args[2:])
if opts.syncWebUIOnly {
cfg := config.DefaultConfig()
workspace := cfg.WorkspacePath()
createWorkspaceTemplates(workspace, true)
fmt.Printf("%s embedded WebUI refreshed in %s\n", logo, filepath.Join(workspace, "webui"))
return
}
configPath := getConfigPath() configPath := getConfigPath()
if _, err := os.Stat(configPath); err == nil { if _, err := os.Stat(configPath); err == nil {
@@ -30,7 +44,7 @@ func onboard() {
} }
workspace := cfg.WorkspacePath() workspace := cfg.WorkspacePath()
createWorkspaceTemplates(workspace) createWorkspaceTemplates(workspace, false)
fmt.Printf("%s clawgo is ready!\n", logo) fmt.Printf("%s clawgo is ready!\n", logo)
fmt.Println("\nNext steps:") fmt.Println("\nNext steps:")
@@ -64,7 +78,17 @@ func ensureConfigOnboard(configPath string, defaults *config.Config) (string, er
return "created", nil return "created", nil
} }
func copyEmbeddedToTarget(targetDir string) error { func parseOnboardOptions(args []string) onboardOptions {
var opts onboardOptions
for _, arg := range args {
if strings.EqualFold(strings.TrimSpace(arg), "--sync-webui") {
opts.syncWebUIOnly = true
}
}
return opts
}
func copyEmbeddedToTarget(targetDir string, overwrite func(relPath string) bool) error {
if err := os.MkdirAll(targetDir, 0755); err != nil { if err := os.MkdirAll(targetDir, 0755); err != nil {
return fmt.Errorf("failed to create target directory: %w", err) return fmt.Errorf("failed to create target directory: %w", err)
} }
@@ -88,7 +112,9 @@ func copyEmbeddedToTarget(targetDir string) error {
} }
targetPath := filepath.Join(targetDir, relPath) targetPath := filepath.Join(targetDir, relPath)
if _, statErr := os.Stat(targetPath); statErr == nil { if _, statErr := os.Stat(targetPath); statErr == nil {
if overwrite == nil || !overwrite(relPath) {
return nil return nil
}
} else if !os.IsNotExist(statErr) { } else if !os.IsNotExist(statErr) {
return statErr return statErr
} }
@@ -104,8 +130,15 @@ func copyEmbeddedToTarget(targetDir string) error {
}) })
} }
func createWorkspaceTemplates(workspace string) { func createWorkspaceTemplates(workspace string, overwriteWebUI bool) {
err := copyEmbeddedToTarget(workspace) var overwrite func(relPath string) bool
if overwriteWebUI {
overwrite = func(relPath string) bool {
relPath = filepath.ToSlash(relPath)
return strings.HasPrefix(relPath, "webui/")
}
}
err := copyEmbeddedToTarget(workspace, overwrite)
if err != nil { if err != nil {
fmt.Printf("Error copying workspace templates: %v\n", err) fmt.Printf("Error copying workspace templates: %v\n", err)
} }

View File

@@ -6,6 +6,15 @@ import (
"clawgo/pkg/config" "clawgo/pkg/config"
) )
func TestParseOnboardOptionsSyncWebUI(t *testing.T) {
t.Parallel()
opts := parseOnboardOptions([]string{"--sync-webui"})
if !opts.syncWebUIOnly {
t.Fatalf("expected sync webui option to be enabled")
}
}
func TestEnsureConfigOnboardGeneratesGatewayToken(t *testing.T) { func TestEnsureConfigOnboardGeneratesGatewayToken(t *testing.T) {
t.Parallel() t.Parallel()

444
install.sh Normal file → Executable file
View File

@@ -1,270 +1,209 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -e set -euo pipefail
# ====================
# Config
# ====================
OWNER="YspCoder" OWNER="YspCoder"
REPO="clawgo" REPO="clawgo"
BIN="clawgo" BIN="clawgo"
INSTALL_DIR="/usr/local/bin" INSTALL_DIR="/usr/local/bin"
WEBUI_DIR="$HOME/.clawgo/workspace/webui" CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/clawgo"
UI_ONLY=0 CONFIG_PATH="$CONFIG_DIR/config.json"
WORKSPACE_DIR="$HOME/.clawgo/workspace"
LEGACY_WORKSPACE_DIR="$HOME/.openclaw/workspace"
usage() { usage() {
cat <<EOF cat <<EOF
Usage: $0 [-ui] Usage: $0
Options: Install or upgrade ClawGo from the latest GitHub release.
-ui Update WebUI only. Skip binary download/install and migration prompt.
-h Show this help message. Notes:
- WebUI is embedded in the binary and initialized when you run 'clawgo onboard'.
- OpenClaw migration is offered only when a legacy workspace is detected.
EOF EOF
} }
while [[ $# -gt 0 ]]; do log() {
case "$1" in printf '%s\n' "$*"
-ui) }
UI_ONLY=1
shift warn() {
;; printf 'Warning: %s\n' "$*" >&2
-h|--help) }
usage
exit 0 require_cmd() {
;; if ! command -v "$1" >/dev/null 2>&1; then
*) warn "Missing required command: $1"
echo "Unknown argument: $1"
usage
exit 1 exit 1
;; fi
esac }
done
# ==================== prompt_yes_no() {
# Detect OS/ARCH local prompt="$1"
# ==================== local default="${2:-N}"
OS="$(uname | tr '[:upper:]' '[:lower:]')" local reply
ARCH="$(uname -m)" if [[ ! -r /dev/tty ]]; then
[[ "$default" =~ ^[Yy]$ ]]
return
fi
if [[ "$default" =~ ^[Yy]$ ]]; then
read -r -p "$prompt [Y/n]: " reply < /dev/tty || true
reply="${reply:-Y}"
else
read -r -p "$prompt [y/N]: " reply < /dev/tty || true
reply="${reply:-N}"
fi
[[ "$reply" =~ ^[Yy]$ ]]
}
case "$ARCH" in tty_read() {
local __var_name="$1"
local __prompt="$2"
local __default="${3:-}"
local __silent="${4:-0}"
local __reply=""
if [[ ! -r /dev/tty ]]; then
printf -v "$__var_name" '%s' "$__default"
return
fi
if [[ "$__silent" == "1" ]]; then
read -r -s -p "$__prompt" __reply < /dev/tty || true
echo > /dev/tty
else
read -r -p "$__prompt" __reply < /dev/tty || true
fi
__reply="${__reply:-$__default}"
printf -v "$__var_name" '%s' "$__reply"
}
detect_platform() {
OS="$(uname | tr '[:upper:]' '[:lower:]')"
ARCH="$(uname -m)"
case "$ARCH" in
x86_64) ARCH="amd64" ;; x86_64) ARCH="amd64" ;;
aarch64|arm64) ARCH="arm64" ;; aarch64|arm64) ARCH="arm64" ;;
*) *)
echo "Unsupported architecture: $ARCH" warn "Unsupported architecture: $ARCH"
exit 1 exit 1
;; ;;
esac esac
case "$OS" in
echo "Detected OS=$OS ARCH=$ARCH" linux|darwin) ;;
*)
# ==================== warn "Unsupported operating system: $OS"
# Check if already installed
# ====================
if [[ "$UI_ONLY" -eq 0 ]]; then
if command -v "$BIN" &> /dev/null; then
echo "$BIN is already installed. Removing existing version..."
sudo rm -f "$INSTALL_DIR/$BIN"
fi
else
echo "UI-only mode enabled: skip binary uninstall/install."
fi
# ====================
# Get Latest GitHub Release
# ====================
echo "Fetching latest release..."
API="https://api.github.com/repos/$OWNER/$REPO/releases/latest"
TAG=$(curl -s "$API" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
if [ -z "$TAG" ]; then
echo "Unable to get latest release tag from GitHub"
exit 1 exit 1
fi ;;
esac
log "Detected OS=$OS ARCH=$ARCH"
}
echo "Latest Release: $TAG" fetch_latest_tag() {
require_cmd curl
local api="https://api.github.com/repos/$OWNER/$REPO/releases/latest"
TAG="$(curl -fsSL "$api" | sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p' | head -n1)"
if [[ -z "${TAG:-}" ]]; then
warn "Unable to get latest release tag from GitHub"
exit 1
fi
log "Latest Release: $TAG"
}
# ==================== install_binary() {
# Construct Download URL for the binary and WebUI local file="${BIN}-${OS}-${ARCH}.tar.gz"
# ==================== local url="https://github.com/$OWNER/$REPO/releases/download/$TAG/$file"
FILE="${BIN}-${OS}-${ARCH}.tar.gz" local out="$TMPDIR/$file"
WEBUI_FILE="webui.tar.gz"
URL="https://github.com/$OWNER/$REPO/releases/download/$TAG/$FILE"
WEBUI_URL="https://github.com/$OWNER/$REPO/releases/download/$TAG/$WEBUI_FILE"
TMPDIR="$(mktemp -d)" log "Downloading $file..."
if [[ "$UI_ONLY" -eq 0 ]]; then curl -fSL "$url" -o "$out"
echo "Trying to download: $URL" tar -xzf "$out" -C "$TMPDIR"
OUT="$TMPDIR/$FILE"
# Now try downloading the file local extracted_bin=""
if curl -fSL "$URL" -o "$OUT"; then
echo "Downloaded $FILE"
tar -xzf "$OUT" -C "$TMPDIR"
EXTRACTED_BIN=""
if [[ -f "$TMPDIR/$BIN" ]]; then if [[ -f "$TMPDIR/$BIN" ]]; then
EXTRACTED_BIN="$TMPDIR/$BIN" extracted_bin="$TMPDIR/$BIN"
else else
EXTRACTED_BIN="$(find "$TMPDIR" -maxdepth 2 -type f -name "${BIN}*" ! -name "*.tar.gz" ! -name "*.zip" | head -n1)" extracted_bin="$(find "$TMPDIR" -maxdepth 2 -type f -name "$BIN" | head -n1)"
fi fi
if [[ -z "$EXTRACTED_BIN" || ! -f "$EXTRACTED_BIN" ]]; then if [[ -z "$extracted_bin" || ! -f "$extracted_bin" ]]; then
echo "Failed to locate extracted binary from $FILE" warn "Failed to locate extracted binary from $file"
exit 1 exit 1
fi fi
chmod +x "$EXTRACTED_BIN" chmod +x "$extracted_bin"
echo "Installing $BIN to $INSTALL_DIR (may require sudo)..." if command -v "$BIN" >/dev/null 2>&1 || [[ -f "$INSTALL_DIR/$BIN" ]]; then
sudo mv "$EXTRACTED_BIN" "$INSTALL_DIR/$BIN" log "Updating existing $BIN in $INSTALL_DIR ..."
echo "Installed $BIN to $INSTALL_DIR/clawgo"
else else
echo "No prebuilt binary found, exiting..." log "Installing $BIN to $INSTALL_DIR ..."
exit 1
fi fi
fi sudo mv "$extracted_bin" "$INSTALL_DIR/$BIN"
log "Installed $BIN to $INSTALL_DIR/$BIN"
}
# ==================== migrate_local_openclaw() {
# Download WebUI local src="${1:-$LEGACY_WORKSPACE_DIR}"
# ==================== local dst="${2:-$WORKSPACE_DIR}"
echo "Downloading ClawGo WebUI..."
WEBUI_OUT="$TMPDIR/$WEBUI_FILE"
if curl -fSL "$WEBUI_URL" -o "$WEBUI_OUT"; then
echo "Downloaded WebUI"
mkdir -p "$WEBUI_DIR"
WEBUI_TMP="$TMPDIR/webui_extract"
rm -rf "$WEBUI_TMP"
mkdir -p "$WEBUI_TMP"
tar -xzf "$WEBUI_OUT" -C "$WEBUI_TMP"
WEBUI_DIST_DIR="" if [[ ! -d "$src" ]]; then
if [[ -d "$WEBUI_TMP/dist" ]]; then warn "OpenClaw workspace not found: $src"
WEBUI_DIST_DIR="$WEBUI_TMP/dist" return 1
else
WEBUI_DIST_DIR="$(find "$WEBUI_TMP" -mindepth 2 -maxdepth 4 -type d -name dist | head -n1)"
fi fi
if [[ -n "$WEBUI_DIST_DIR" && -d "$WEBUI_DIST_DIR" ]]; then log "[INFO] source: $src"
rsync -a --delete "$WEBUI_DIST_DIR/" "$WEBUI_DIR/" log "[INFO] target: $dst"
else mkdir -p "$dst" "$dst/memory"
rsync -a --delete "$WEBUI_TMP/" "$WEBUI_DIR/" local ts
fi ts="$(date -u +%Y%m%dT%H%M%SZ)"
echo "WebUI installed to $WEBUI_DIR" local backup_dir="$dst/.migration-backup-$ts"
else mkdir -p "$backup_dir"
echo "Failed to download WebUI"
exit 1
fi
# ====================
# Migrate (Embedded openclaw2clawgo Script)
# ====================
if [[ "$UI_ONLY" -eq 0 ]]; then
read -p "Do you want to migrate your OpenClaw workspace to ClawGo? (y/n): " MIGRATE
if [[ "$MIGRATE" == "y" || "$MIGRATE" == "Y" ]]; then
echo "Choose migration type: "
echo "1. Local migration"
echo "2. Remote migration"
read -p "Enter your choice (1 or 2): " MIGRATION_TYPE
case "$MIGRATION_TYPE" in
1)
echo "Proceeding with local migration..."
# Default paths for local migration
SRC_DEFAULT="$HOME/.openclaw/workspace"
DST_DEFAULT="$HOME/.clawgo/workspace"
SRC="${SRC_DEFAULT}"
DST="${DST_DEFAULT}"
# Prompt user about overwriting existing data
echo "Warning: Migration will overwrite the contents of $DST"
read -p "Are you sure you want to continue? (y/n): " CONFIRM
if [[ "$CONFIRM" != "y" && "$CONFIRM" != "Y" ]]; then
echo "Migration canceled."
exit 0
fi
echo "[INFO] source: $SRC"
echo "[INFO] target: $DST"
mkdir -p "$DST" "$DST/memory"
TS="$(date -u +%Y%m%dT%H%M%SZ)"
BACKUP_DIR="$DST/.migration-backup-$TS"
mkdir -p "$BACKUP_DIR"
# Backup existing key files if present
for f in AGENTS.md SOUL.md USER.md IDENTITY.md TOOLS.md MEMORY.md HEARTBEAT.md; do for f in AGENTS.md SOUL.md USER.md IDENTITY.md TOOLS.md MEMORY.md HEARTBEAT.md; do
if [[ -f "$DST/$f" ]]; then if [[ -f "$dst/$f" ]]; then
cp -a "$DST/$f" "$BACKUP_DIR/$f" cp -a "$dst/$f" "$backup_dir/$f"
fi fi
done done
if [[ -d "$DST/memory" ]]; then if [[ -d "$dst/memory" ]]; then
cp -a "$DST/memory" "$BACKUP_DIR/memory" || true cp -a "$dst/memory" "$backup_dir/memory" || true
fi fi
# Migrate core persona/context files
for f in AGENTS.md SOUL.md USER.md IDENTITY.md TOOLS.md MEMORY.md HEARTBEAT.md; do for f in AGENTS.md SOUL.md USER.md IDENTITY.md TOOLS.md MEMORY.md HEARTBEAT.md; do
if [[ -f "$SRC/$f" ]]; then if [[ -f "$src/$f" ]]; then
cp -a "$SRC/$f" "$DST/$f" cp -a "$src/$f" "$dst/$f"
echo "[OK] migrated $f" log "[OK] migrated $f"
fi fi
done done
if [[ -d "$src/memory" ]]; then
# Merge memory directory rsync -a "$src/memory/" "$dst/memory/"
if [[ -d "$SRC/memory" ]]; then log "[OK] migrated memory/"
rsync -a "$SRC/memory/" "$DST/memory/"
echo "[OK] migrated memory/"
fi fi
# Optional: sync into embedded workspace template used by clawgo builds log "[DONE] local migration complete"
echo "[INFO] Syncing embed workspace template..." }
if [[ -d "$DST" ]]; then
mkdir -p "$DST"
for f in AGENTS.md SOUL.md USER.md IDENTITY.md TOOLS.md MEMORY.md HEARTBEAT.md; do
if [[ -f "$DST/$f" ]]; then
cp -a "$DST/$f" "$DST/$f"
fi
done
if [[ -d "$DST/memory" ]]; then
mkdir -p "$DST/memory"
rsync -a "$DST/memory/" "$DST/memory/"
fi
echo "[OK] synced embed workspace template"
fi
echo "[DONE] migration complete" migrate_remote_openclaw() {
;; require_cmd sshpass
2) require_cmd scp
echo "Proceeding with remote migration..." require_cmd ssh
read -p "Enter remote host (e.g., user@hostname): " REMOTE_HOST local remote_host remote_port remote_pass migration_script
read -p "Enter remote port (default 22): " REMOTE_PORT tty_read remote_host "Enter remote host (e.g. user@hostname): "
REMOTE_PORT="${REMOTE_PORT:-22}" tty_read remote_port "Enter remote port (default 22): " "22"
read -sp "Enter remote password: " REMOTE_PASS tty_read remote_pass "Enter remote password: " "" "1"
echo
# Create a temporary SSH key for non-interactive SSH authentication (assuming sshpass is installed)
SSH_KEY=$(mktemp)
sshpass -p "$REMOTE_PASS" ssh-copy-id -i "$SSH_KEY" -p "$REMOTE_PORT" "$REMOTE_HOST"
# Prepare migration script
MIGRATION_SCRIPT="$TMPDIR/openclaw2clawgo.sh"
cat << 'EOF' > "$MIGRATION_SCRIPT"
#!/bin/bash
set -e
migration_script="$TMPDIR/openclaw2clawgo.sh"
cat > "$migration_script" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
SRC_DEFAULT="$HOME/.openclaw/workspace" SRC_DEFAULT="$HOME/.openclaw/workspace"
DST_DEFAULT="$HOME/.clawgo/workspace" DST_DEFAULT="$HOME/.clawgo/workspace"
SRC="${1:-$SRC_DEFAULT}" SRC="${1:-$SRC_DEFAULT}"
DST="${2:-$DST_DEFAULT}" DST="${2:-$DST_DEFAULT}"
echo "[INFO] source: $SRC"
echo "[INFO] target: $DST"
mkdir -p "$DST" "$DST/memory" mkdir -p "$DST" "$DST/memory"
TS="$(date -u +%Y%m%dT%H%M%SZ)" TS="$(date -u +%Y%m%dT%H%M%SZ)"
BACKUP_DIR="$DST/.migration-backup-$TS" BACKUP_DIR="$DST/.migration-backup-$TS"
mkdir -p "$BACKUP_DIR" mkdir -p "$BACKUP_DIR"
# Backup existing key files if present
for f in AGENTS.md SOUL.md USER.md IDENTITY.md TOOLS.md MEMORY.md HEARTBEAT.md; do for f in AGENTS.md SOUL.md USER.md IDENTITY.md TOOLS.md MEMORY.md HEARTBEAT.md; do
if [[ -f "$DST/$f" ]]; then if [[ -f "$DST/$f" ]]; then
cp -a "$DST/$f" "$BACKUP_DIR/$f" cp -a "$DST/$f" "$BACKUP_DIR/$f"
@@ -273,44 +212,99 @@ done
if [[ -d "$DST/memory" ]]; then if [[ -d "$DST/memory" ]]; then
cp -a "$DST/memory" "$BACKUP_DIR/memory" || true cp -a "$DST/memory" "$BACKUP_DIR/memory" || true
fi fi
fi
# Migrate core persona/context files
for f in AGENTS.md SOUL.md USER.md IDENTITY.md TOOLS.md MEMORY.md HEARTBEAT.md; do for f in AGENTS.md SOUL.md USER.md IDENTITY.md TOOLS.md MEMORY.md HEARTBEAT.md; do
if [[ -f "$SRC/$f" ]]; then if [[ -f "$SRC/$f" ]]; then
cp -a "$SRC/$f" "$DST/$f" cp -a "$SRC/$f" "$DST/$f"
echo "[OK] migrated $f" echo "[OK] migrated $f"
fi fi
done done
# Merge memory directory
if [[ -d "$SRC/memory" ]]; then if [[ -d "$SRC/memory" ]]; then
rsync -a "$SRC/memory/" "$DST/memory/" rsync -a "$SRC/memory/" "$DST/memory/"
echo "[OK] migrated memory/" echo "[OK] migrated memory/"
fi fi
echo "[DONE] remote migration complete"
echo "[DONE] migration complete"
EOF EOF
# Copy migration script to remote server and execute it chmod +x "$migration_script"
sshpass -p "$REMOTE_PASS" scp -P "$REMOTE_PORT" "$MIGRATION_SCRIPT" "$REMOTE_HOST:/tmp/openclaw2clawgo.sh" sshpass -p "$remote_pass" scp -P "$remote_port" "$migration_script" "$remote_host:/tmp/openclaw2clawgo.sh"
sshpass -p "$REMOTE_PASS" ssh -p "$REMOTE_PORT" "$REMOTE_HOST" "bash /tmp/openclaw2clawgo.sh" sshpass -p "$remote_pass" ssh -p "$remote_port" "$remote_host" "bash /tmp/openclaw2clawgo.sh"
log "[INFO] Remote migration completed."
}
echo "[INFO] Remote migration completed." offer_openclaw_migration() {
if [[ ! -d "$LEGACY_WORKSPACE_DIR" ]]; then
return
fi
if ! prompt_yes_no "Detected OpenClaw workspace at $LEGACY_WORKSPACE_DIR. Migrate it to ClawGo now?" "N"; then
return
fi
local migration_type
if [[ -r /dev/tty ]]; then
log "Choose migration type:"
log " 1. Local migration"
log " 2. Remote migration"
tty_read migration_type "Enter your choice (1 or 2): " "1"
else
migration_type="1"
fi
case "$migration_type" in
1)
warn "Migration will overwrite files in $WORKSPACE_DIR when names collide."
if prompt_yes_no "Continue local migration?" "N"; then
migrate_local_openclaw
else
log "Migration skipped."
fi
;;
2)
migrate_remote_openclaw
;; ;;
*) *)
echo "Invalid choice. Skipping migration." log "Invalid choice. Skipping migration."
;; ;;
esac esac
fi }
fi
echo "Cleaning up..." offer_onboard() {
rm -rf "$TMPDIR" log "Refreshing embedded WebUI assets..."
"$INSTALL_DIR/$BIN" onboard --sync-webui >/dev/null 2>&1 || warn "Failed to refresh embedded WebUI assets automatically. You can run 'clawgo onboard --sync-webui' later."
echo "Done 🎉" if [[ -f "$CONFIG_PATH" ]]; then
if [[ "$UI_ONLY" -eq 0 ]]; then log "Existing config detected at $CONFIG_PATH"
echo "Run 'clawgo --help' to verify" log "WebUI assets were refreshed from the embedded bundle."
else log "Run 'clawgo onboard' only if you want to regenerate config or missing workspace templates."
echo "WebUI update finished." return
fi fi
log "WebUI assets were refreshed from the embedded bundle."
if prompt_yes_no "No config found. Run 'clawgo onboard' now?" "N"; then
"$INSTALL_DIR/$BIN" onboard
else
log "Skipped onboard. Run 'clawgo onboard' when you are ready."
fi
}
main() {
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
usage
exit 0
fi
detect_platform
fetch_latest_tag
TMPDIR="$(mktemp -d)"
trap 'rm -rf "$TMPDIR"' EXIT
install_binary
offer_openclaw_migration
offer_onboard
log "Done."
log "Run 'clawgo --help' to verify the installation."
}
main "$@"