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 {
return nil if overwrite == nil || !overwrite(relPath) {
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()

476
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"
exit 1
fi
}
prompt_yes_no() {
local prompt="$1"
local default="${2:-N}"
local reply
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]$ ]]
}
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" ;;
aarch64|arm64) ARCH="arm64" ;;
*) *)
echo "Unknown argument: $1" warn "Unsupported architecture: $ARCH"
usage
exit 1 exit 1
;; ;;
esac esac
done case "$OS" in
linux|darwin) ;;
# ==================== *)
# Detect OS/ARCH warn "Unsupported operating system: $OS"
# ====================
OS="$(uname | tr '[:upper:]' '[:lower:]')"
ARCH="$(uname -m)"
case "$ARCH" in
x86_64) ARCH="amd64" ;;
aarch64|arm64) ARCH="arm64" ;;
*)
echo "Unsupported architecture: $ARCH"
exit 1
;;
esac
echo "Detected OS=$OS ARCH=$ARCH"
# ====================
# 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
fi
echo "Latest Release: $TAG"
# ====================
# Construct Download URL for the binary and WebUI
# ====================
FILE="${BIN}-${OS}-${ARCH}.tar.gz"
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)"
if [[ "$UI_ONLY" -eq 0 ]]; then
echo "Trying to download: $URL"
OUT="$TMPDIR/$FILE"
# Now try downloading the file
if curl -fSL "$URL" -o "$OUT"; then
echo "Downloaded $FILE"
tar -xzf "$OUT" -C "$TMPDIR"
EXTRACTED_BIN=""
if [[ -f "$TMPDIR/$BIN" ]]; then
EXTRACTED_BIN="$TMPDIR/$BIN"
else
EXTRACTED_BIN="$(find "$TMPDIR" -maxdepth 2 -type f -name "${BIN}*" ! -name "*.tar.gz" ! -name "*.zip" | head -n1)"
fi
if [[ -z "$EXTRACTED_BIN" || ! -f "$EXTRACTED_BIN" ]]; then
echo "Failed to locate extracted binary from $FILE"
exit 1 exit 1
fi ;;
esac
log "Detected OS=$OS ARCH=$ARCH"
}
chmod +x "$EXTRACTED_BIN" fetch_latest_tag() {
echo "Installing $BIN to $INSTALL_DIR (may require sudo)..." require_cmd curl
sudo mv "$EXTRACTED_BIN" "$INSTALL_DIR/$BIN" local api="https://api.github.com/repos/$OWNER/$REPO/releases/latest"
echo "Installed $BIN to $INSTALL_DIR/clawgo" TAG="$(curl -fsSL "$api" | sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p' | head -n1)"
else if [[ -z "${TAG:-}" ]]; then
echo "No prebuilt binary found, exiting..." warn "Unable to get latest release tag from GitHub"
exit 1 exit 1
fi fi
fi log "Latest Release: $TAG"
}
# ==================== install_binary() {
# Download WebUI local file="${BIN}-${OS}-${ARCH}.tar.gz"
# ==================== local url="https://github.com/$OWNER/$REPO/releases/download/$TAG/$file"
echo "Downloading ClawGo WebUI..." local out="$TMPDIR/$file"
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="" log "Downloading $file..."
if [[ -d "$WEBUI_TMP/dist" ]]; then curl -fSL "$url" -o "$out"
WEBUI_DIST_DIR="$WEBUI_TMP/dist" tar -xzf "$out" -C "$TMPDIR"
local extracted_bin=""
if [[ -f "$TMPDIR/$BIN" ]]; then
extracted_bin="$TMPDIR/$BIN"
else else
WEBUI_DIST_DIR="$(find "$WEBUI_TMP" -mindepth 2 -maxdepth 4 -type d -name dist | head -n1)" extracted_bin="$(find "$TMPDIR" -maxdepth 2 -type f -name "$BIN" | head -n1)"
fi fi
if [[ -n "$WEBUI_DIST_DIR" && -d "$WEBUI_DIST_DIR" ]]; then if [[ -z "$extracted_bin" || ! -f "$extracted_bin" ]]; then
rsync -a --delete "$WEBUI_DIST_DIR/" "$WEBUI_DIR/" warn "Failed to locate extracted binary from $file"
else exit 1
rsync -a --delete "$WEBUI_TMP/" "$WEBUI_DIR/"
fi fi
echo "WebUI installed to $WEBUI_DIR"
else
echo "Failed to download WebUI"
exit 1
fi
# ==================== chmod +x "$extracted_bin"
# Migrate (Embedded openclaw2clawgo Script) if command -v "$BIN" >/dev/null 2>&1 || [[ -f "$INSTALL_DIR/$BIN" ]]; then
# ==================== log "Updating existing $BIN in $INSTALL_DIR ..."
if [[ "$UI_ONLY" -eq 0 ]]; then else
read -p "Do you want to migrate your OpenClaw workspace to ClawGo? (y/n): " MIGRATE log "Installing $BIN to $INSTALL_DIR ..."
if [[ "$MIGRATE" == "y" || "$MIGRATE" == "Y" ]]; then fi
echo "Choose migration type: " sudo mv "$extracted_bin" "$INSTALL_DIR/$BIN"
echo "1. Local migration" log "Installed $BIN to $INSTALL_DIR/$BIN"
echo "2. Remote migration" }
read -p "Enter your choice (1 or 2): " MIGRATION_TYPE
case "$MIGRATION_TYPE" in migrate_local_openclaw() {
1) local src="${1:-$LEGACY_WORKSPACE_DIR}"
echo "Proceeding with local migration..." local dst="${2:-$WORKSPACE_DIR}"
# Default paths for local migration if [[ ! -d "$src" ]]; then
SRC_DEFAULT="$HOME/.openclaw/workspace" warn "OpenClaw workspace not found: $src"
DST_DEFAULT="$HOME/.clawgo/workspace" return 1
SRC="${SRC_DEFAULT}" fi
DST="${DST_DEFAULT}"
# Prompt user about overwriting existing data log "[INFO] source: $src"
echo "Warning: Migration will overwrite the contents of $DST" log "[INFO] target: $dst"
read -p "Are you sure you want to continue? (y/n): " CONFIRM mkdir -p "$dst" "$dst/memory"
if [[ "$CONFIRM" != "y" && "$CONFIRM" != "Y" ]]; then local ts
echo "Migration canceled." ts="$(date -u +%Y%m%dT%H%M%SZ)"
exit 0 local backup_dir="$dst/.migration-backup-$ts"
fi mkdir -p "$backup_dir"
echo "[INFO] source: $SRC" for f in AGENTS.md SOUL.md USER.md IDENTITY.md TOOLS.md MEMORY.md HEARTBEAT.md; do
echo "[INFO] target: $DST" if [[ -f "$dst/$f" ]]; then
cp -a "$dst/$f" "$backup_dir/$f"
fi
done
if [[ -d "$dst/memory" ]]; then
cp -a "$dst/memory" "$backup_dir/memory" || true
fi
mkdir -p "$DST" "$DST/memory" for f in AGENTS.md SOUL.md USER.md IDENTITY.md TOOLS.md MEMORY.md HEARTBEAT.md; do
TS="$(date -u +%Y%m%dT%H%M%SZ)" if [[ -f "$src/$f" ]]; then
BACKUP_DIR="$DST/.migration-backup-$TS" cp -a "$src/$f" "$dst/$f"
mkdir -p "$BACKUP_DIR" log "[OK] migrated $f"
fi
done
if [[ -d "$src/memory" ]]; then
rsync -a "$src/memory/" "$dst/memory/"
log "[OK] migrated memory/"
fi
# Backup existing key files if present log "[DONE] local migration complete"
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" "$BACKUP_DIR/$f"
fi
done
if [[ -d "$DST/memory" ]]; then
cp -a "$DST/memory" "$BACKUP_DIR/memory" || true
fi
# Migrate core persona/context files migrate_remote_openclaw() {
for f in AGENTS.md SOUL.md USER.md IDENTITY.md TOOLS.md MEMORY.md HEARTBEAT.md; do require_cmd sshpass
if [[ -f "$SRC/$f" ]]; then require_cmd scp
cp -a "$SRC/$f" "$DST/$f" require_cmd ssh
echo "[OK] migrated $f"
fi
done
# Merge memory directory local remote_host remote_port remote_pass migration_script
if [[ -d "$SRC/memory" ]]; then tty_read remote_host "Enter remote host (e.g. user@hostname): "
rsync -a "$SRC/memory/" "$DST/memory/" tty_read remote_port "Enter remote port (default 22): " "22"
echo "[OK] migrated memory/" tty_read remote_pass "Enter remote password: " "" "1"
fi
# Optional: sync into embedded workspace template used by clawgo builds
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"
;;
2)
echo "Proceeding with remote migration..."
read -p "Enter remote host (e.g., user@hostname): " REMOTE_HOST
read -p "Enter remote port (default 22): " REMOTE_PORT
REMOTE_PORT="${REMOTE_PORT:-22}"
read -sp "Enter remote password: " REMOTE_PASS
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 "$@"