diff --git a/examples/openclaw-memory-plugin/INSTALL-ZH.md b/examples/openclaw-memory-plugin/INSTALL-ZH.md index 241fde3c..18bcd2c1 100644 --- a/examples/openclaw-memory-plugin/INSTALL-ZH.md +++ b/examples/openclaw-memory-plugin/INSTALL-ZH.md @@ -4,6 +4,24 @@ --- +## 一键安装(Linux / macOS) + +**前置条件:** Python >= 3.10、Node.js >= 22。脚本会自动校验这些依赖,若有缺失会给出安装指引。 + +```bash +curl -fsSL https://raw.githubusercontent.com/volcengine/OpenViking/main/examples/openclaw-memory-plugin/install.sh | bash +``` + +非交互模式(使用默认配置): + +```bash +curl -fsSL https://raw.githubusercontent.com/volcengine/OpenViking/main/examples/openclaw-memory-plugin/install.sh | bash -s -y +``` + +脚本会:1) 校验 OpenViking 运行环境(并检查是否已安装 OpenClaw);2) 仅安装 OpenViking;3) 配置并部署记忆插件。 + +--- + ## 一、快速开始(让 OpenClaw 自动安装) 先将技能文件复制到 OpenClaw 技能目录,再让 OpenClaw 完成后续步骤: diff --git a/examples/openclaw-memory-plugin/INSTALL.md b/examples/openclaw-memory-plugin/INSTALL.md index 0c8b0e31..1030693f 100644 --- a/examples/openclaw-memory-plugin/INSTALL.md +++ b/examples/openclaw-memory-plugin/INSTALL.md @@ -4,6 +4,24 @@ Give [OpenClaw](https://github.com/openclaw/openclaw) long-term memory powered b --- +## One-Click Install (Linux / macOS) + +**Prerequisites:** Python >= 3.10, Node.js >= 22. The script checks these and prompts you to install any missing components. + +```bash +curl -fsSL https://raw.githubusercontent.com/volcengine/OpenViking/main/examples/openclaw-memory-plugin/install.sh | bash +``` + +Non-interactive mode: + +```bash +curl -fsSL https://raw.githubusercontent.com/volcengine/OpenViking/main/examples/openclaw-memory-plugin/install.sh | bash -s -y +``` + +The script will: 1) validate the OpenViking runtime environment (and check that OpenClaw is installed), 2) install OpenViking only, 3) configure and deploy the memory plugin. + +--- + ## 1. Quick Start (Let OpenClaw Install It) Copy the skill file into OpenClaw's skill directory, then let OpenClaw handle the rest: diff --git a/examples/openclaw-memory-plugin/install.ps1 b/examples/openclaw-memory-plugin/install.ps1 new file mode 100644 index 00000000..eb83fd1b --- /dev/null +++ b/examples/openclaw-memory-plugin/install.ps1 @@ -0,0 +1,385 @@ +param( + [switch]$Yes, + [switch]$Zh +) + +$ErrorActionPreference = "Stop" + +function T { + param( + [string]$En, + [string]$ZhText + ) + if ($Zh) { return $ZhText } + return $En +} + +function Info($m) { Write-Host "[INFO] $m" -ForegroundColor Green } +function Warn($m) { Write-Host "[WARN] $m" -ForegroundColor Yellow } +function Err($m) { Write-Host "[ERROR] $m" -ForegroundColor Red } +function Title($m) { Write-Host $m -ForegroundColor Cyan } +function Write-Utf8NoBom { + param( + [string]$Path, + [string]$Content + ) + $enc = New-Object System.Text.UTF8Encoding($false) + [System.IO.File]::WriteAllText($Path, $Content, $enc) +} + +$Repo = if ($env:REPO) { $env:REPO } else { "volcengine/OpenViking" } +$Branch = if ($env:BRANCH) { $env:BRANCH } else { "main" } +$NpmRegistry = if ($env:NPM_REGISTRY) { $env:NPM_REGISTRY } else { "https://registry.npmmirror.com" } +$PipIndexUrl = if ($env:PIP_INDEX_URL) { $env:PIP_INDEX_URL } else { "https://pypi.tuna.tsinghua.edu.cn/simple" } + +$HomeDir = if ($env:USERPROFILE) { $env:USERPROFILE } else { $HOME } +$OpenClawDir = Join-Path $HomeDir ".openclaw" +$OpenVikingDir = Join-Path $HomeDir ".openviking" +$PluginDest = Join-Path $OpenClawDir "extensions\memory-openviking" + +$DefaultServerPort = 1933 +$DefaultAgfsPort = 1833 +$DefaultVlmModel = "doubao-seed-1-8-251228" +$DefaultEmbeddingModel = "doubao-embedding-vision-250615" + +function Get-PythonCommand { + if ($env:OPENVIKING_PYTHON) { return $env:OPENVIKING_PYTHON } + if (Get-Command python -ErrorAction SilentlyContinue) { return "python" } + if (Get-Command python3 -ErrorAction SilentlyContinue) { return "python3" } + return $null +} + +function Check-Python { + $py = Get-PythonCommand + if (-not $py) { + return @{ Ok = $false; Detail = (T "Python not found. Install Python >= 3.10." "Python 未找到,请安装 Python >= 3.10") } + } + try { + $v = & $py -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" 2>$null + if (-not $v) { + return @{ Ok = $false; Detail = (T "Python command failed." "Python 命令执行失败") } + } + $parts = $v.Trim().Split(".") + $major = [int]$parts[0] + $minor = [int]$parts[1] + if ($major -lt 3 -or ($major -eq 3 -and $minor -lt 10)) { + return @{ Ok = $false; Detail = (T "Python $v is too old. Need >= 3.10." "Python 版本 $v 过低,需要 >= 3.10") } + } + return @{ Ok = $true; Detail = "$v ($py)"; Cmd = $py } + } catch { + return @{ Ok = $false; Detail = $_.Exception.Message } + } +} + +function Check-Node { + try { + if (-not (Get-Command node -ErrorAction SilentlyContinue)) { + return @{ Ok = $false; Detail = (T "Node.js not found. Install Node.js >= 22." "Node.js 未找到,请安装 Node.js >= 22") } + } + $v = (node -v).Trim() + $major = [int]($v.TrimStart("v").Split(".")[0]) + if ($major -lt 22) { + return @{ Ok = $false; Detail = (T "Node.js $v is too old. Need >= 22." "Node.js 版本 $v 过低,需要 >= 22") } + } + return @{ Ok = $true; Detail = $v } + } catch { + return @{ Ok = $false; Detail = $_.Exception.Message } + } +} + +function Validate-Environment { + Info (T "Checking OpenViking runtime environment..." "正在校验 OpenViking 运行环境...") + Write-Host "" + + $missing = @() + + $py = Check-Python + if ($py.Ok) { + Info (" Python: {0} ✓" -f $py.Detail) + } else { + $missing += "Python >= 3.10" + Err (" {0}" -f $py.Detail) + } + + $node = Check-Node + if ($node.Ok) { + Info (" Node.js: {0} ✓" -f $node.Detail) + } else { + $missing += "Node.js >= 22" + Err (" {0}" -f $node.Detail) + } + + if ($missing.Count -gt 0) { + Write-Host "" + Err (T "Environment check failed. Install missing dependencies first." "环境校验未通过,请先安装以下缺失组件。") + Write-Host "" + if ($missing -contains "Python >= 3.10") { + Write-Host (T "Python (example via winget):" "Python(可使用 winget 安装示例):") + Write-Host " winget install --id Python.Python.3.11 -e" + Write-Host "" + } + if ($missing -contains "Node.js >= 22") { + Write-Host (T "Node.js (example via nvm-windows):" "Node.js(可使用 nvm-windows 安装示例):") + Write-Host " nvm install 22.22.0" + Write-Host " nvm use 22.22.0" + Write-Host "" + } + exit 1 + } + + Write-Host "" + Info (T "Environment check passed ✓" "环境校验通过 ✓") + Write-Host "" +} + +function Check-OpenClaw { + if ($env:SKIP_OPENCLAW -eq "1") { + Info (T "Skipping OpenClaw check (SKIP_OPENCLAW=1)" "跳过 OpenClaw 校验 (SKIP_OPENCLAW=1)") + return + } + + Info (T "Checking OpenClaw..." "正在校验 OpenClaw...") + if (Get-Command openclaw -ErrorAction SilentlyContinue) { + Info (T "OpenClaw detected ✓" "OpenClaw 已安装 ✓") + return + } + + Err (T "OpenClaw not found. Install it manually, then rerun this script." "未检测到 OpenClaw,请先手动安装后再执行本脚本") + Write-Host "" + Write-Host (T "Recommended command:" "推荐命令:") + Write-Host " npm install -g openclaw --registry $NpmRegistry" + Write-Host "" + Write-Host " openclaw --version" + Write-Host " openclaw onboard" + Write-Host "" + exit 1 +} + +function Install-OpenViking { + if ($env:SKIP_OPENVIKING -eq "1") { + Info (T "Skipping OpenViking install (SKIP_OPENVIKING=1)" "跳过 OpenViking 安装 (SKIP_OPENVIKING=1)") + return + } + + $py = (Check-Python).Cmd + Info (T "Installing OpenViking from PyPI..." "正在安装 OpenViking (PyPI)...") + Info ("{0} {1}" -f (T "Using pip index:" "使用 pip 镜像源:"), $PipIndexUrl) + & $py -m pip install --upgrade pip -i $PipIndexUrl | Out-Host + & $py -m pip install openviking -i $PipIndexUrl | Out-Host + Info (T "OpenViking installed ✓" "OpenViking 安装完成 ✓") +} + +function Prompt-OrDefault { + param( + [string]$PromptText, + [string]$DefaultValue + ) + $v = Read-Host "$PromptText [$DefaultValue]" + if ([string]::IsNullOrWhiteSpace($v)) { return $DefaultValue } + return $v.Trim() +} + +function Prompt-Optional { + param([string]$PromptText) + $v = Read-Host $PromptText + if ([string]::IsNullOrWhiteSpace($v)) { return "" } + return $v.Trim() +} + +function Configure-OvConf { + New-Item -ItemType Directory -Force -Path $OpenVikingDir | Out-Null + + $workspace = Join-Path $OpenVikingDir "data" + $serverPort = "$DefaultServerPort" + $agfsPort = "$DefaultAgfsPort" + $vlmModel = $DefaultVlmModel + $embeddingModel = $DefaultEmbeddingModel + + $legacyKey = if ($env:OPENVIKING_ARK_API_KEY) { $env:OPENVIKING_ARK_API_KEY } else { "" } + $vlmApiKey = if ($env:OPENVIKING_VLM_API_KEY) { $env:OPENVIKING_VLM_API_KEY } else { $legacyKey } + $embeddingApiKey = if ($env:OPENVIKING_EMBEDDING_API_KEY) { $env:OPENVIKING_EMBEDDING_API_KEY } else { $legacyKey } + + if (-not $Yes) { + Write-Host "" + $workspace = Prompt-OrDefault (T "OpenViking workspace path" "OpenViking 数据目录") $workspace + $serverPort = Prompt-OrDefault (T "OpenViking HTTP port" "OpenViking HTTP 端口") $serverPort + $agfsPort = Prompt-OrDefault (T "AGFS port" "AGFS 端口") $agfsPort + $vlmModel = Prompt-OrDefault (T "VLM model" "VLM 模型") $vlmModel + $embeddingModel = Prompt-OrDefault (T "Embedding model" "Embedding 模型") $embeddingModel + Write-Host (T "VLM and Embedding API keys can differ. You can leave either empty and edit ov.conf later." "说明:VLM 与 Embedding 的 API Key 可能不同,可分别填写;留空后续可在 ov.conf 修改。") + $vlmInput = Prompt-Optional (T "VLM API key (optional)" "VLM API Key(可留空)") + $embInput = Prompt-Optional (T "Embedding API key (optional)" "Embedding API Key(可留空)") + if ($vlmInput) { $vlmApiKey = $vlmInput } + if ($embInput) { $embeddingApiKey = $embInput } + } + + New-Item -ItemType Directory -Force -Path $workspace | Out-Null + + $cfg = @{ + server = @{ + host = "127.0.0.1" + port = [int]$serverPort + root_api_key = $null + cors_origins = @("*") + } + storage = @{ + workspace = $workspace + vectordb = @{ name = "context"; backend = "local"; project = "default" } + agfs = @{ port = [int]$agfsPort; log_level = "warn"; backend = "local"; timeout = 10; retry_times = 3 } + } + embedding = @{ + dense = @{ + backend = "volcengine" + api_key = $(if ($embeddingApiKey) { $embeddingApiKey } else { $null }) + model = $embeddingModel + api_base = "https://ark.cn-beijing.volces.com/api/v3" + dimension = 1024 + input = "multimodal" + } + } + vlm = @{ + backend = "volcengine" + api_key = $(if ($vlmApiKey) { $vlmApiKey } else { $null }) + model = $vlmModel + api_base = "https://ark.cn-beijing.volces.com/api/v3" + temperature = 0.1 + max_retries = 3 + } + } + + $confPath = Join-Path $OpenVikingDir "ov.conf" + $cfgJson = $cfg | ConvertTo-Json -Depth 10 + Write-Utf8NoBom -Path $confPath -Content $cfgJson + Info ("{0} {1}" -f (T "Config generated:" "已生成配置:"), $confPath) + return [int]$serverPort +} + +function Download-Plugin { + $rawBase = "https://raw.githubusercontent.com/$Repo/$Branch" + $files = @( + "examples/openclaw-memory-plugin/index.ts", + "examples/openclaw-memory-plugin/config.ts", + "examples/openclaw-memory-plugin/openclaw.plugin.json", + "examples/openclaw-memory-plugin/package.json", + "examples/openclaw-memory-plugin/package-lock.json", + "examples/openclaw-memory-plugin/.gitignore" + ) + + New-Item -ItemType Directory -Force -Path $PluginDest | Out-Null + Info (T "Downloading memory-openviking plugin..." "正在下载 memory-openviking 插件...") + Info ("{0} $Repo@$Branch" -f (T "Plugin source:" "插件来源:")) + + foreach ($rel in $files) { + $name = Split-Path $rel -Leaf + $url = "$rawBase/$rel" + $dst = Join-Path $PluginDest $name + try { + Invoke-WebRequest -Uri $url -OutFile $dst -UseBasicParsing | Out-Null + } catch { + Err ("{0} $url" -f (T "Download failed:" "下载失败:")) + throw + } + } + + Push-Location $PluginDest + try { + npm install --no-audit --no-fund | Out-Host + } finally { + Pop-Location + } + Info ("{0} $PluginDest" -f (T "Plugin deployed:" "插件部署完成:")) +} + +function Configure-OpenClawPlugin { + param([int]$ServerPort) + Info (T "Configuring OpenClaw plugin..." "正在配置 OpenClaw 插件...") + + $cfgPath = Join-Path $OpenClawDir "openclaw.json" + $cfg = @{} + if (Test-Path $cfgPath) { + try { + $raw = Get-Content -Raw -Path $cfgPath + if (-not [string]::IsNullOrWhiteSpace($raw)) { + $obj = $raw | ConvertFrom-Json -AsHashtable + if ($obj) { $cfg = $obj } + } + } catch { + Warn (T "Existing openclaw.json is invalid. Rebuilding required sections." "检测到已有 openclaw.json 非法,将重建相关配置节点。") + } + } + + if (-not $cfg.ContainsKey("plugins")) { $cfg["plugins"] = @{} } + if (-not $cfg.ContainsKey("gateway")) { $cfg["gateway"] = @{} } + if (-not $cfg["plugins"].ContainsKey("slots")) { $cfg["plugins"]["slots"] = @{} } + if (-not $cfg["plugins"].ContainsKey("load")) { $cfg["plugins"]["load"] = @{} } + if (-not $cfg["plugins"].ContainsKey("entries")) { $cfg["plugins"]["entries"] = @{} } + + # Keep plugin load paths unique. + $existingPaths = @() + if ($cfg["plugins"]["load"].ContainsKey("paths") -and $cfg["plugins"]["load"]["paths"]) { + $existingPaths = @($cfg["plugins"]["load"]["paths"]) + } + $mergedPaths = @($existingPaths + @($PluginDest) | Select-Object -Unique) + + $cfg["plugins"]["enabled"] = $true + $cfg["plugins"]["allow"] = @("memory-openviking") + $cfg["plugins"]["slots"]["memory"] = "memory-openviking" + $cfg["plugins"]["load"]["paths"] = $mergedPaths + $cfg["plugins"]["entries"]["memory-openviking"] = @{ + config = @{ + mode = "local" + configPath = "~/.openviking/ov.conf" + port = $ServerPort + targetUri = "viking://" + autoRecall = $true + autoCapture = $true + } + } + $cfg["gateway"]["mode"] = "local" + + $cfgJson = $cfg | ConvertTo-Json -Depth 20 + Write-Utf8NoBom -Path $cfgPath -Content $cfgJson + + Info (T "OpenClaw plugin configured" "OpenClaw 插件配置完成") +} + +function Write-OpenVikingEnv { + $pyCmd = Get-PythonCommand + $pyPath = "" + if ($pyCmd) { + $g = Get-Command $pyCmd -ErrorAction SilentlyContinue + if ($g) { $pyPath = $g.Source } + } + + New-Item -ItemType Directory -Force -Path $OpenClawDir | Out-Null + $envPath = Join-Path $OpenClawDir "openviking.env.ps1" + $envContent = '$env:OPENVIKING_PYTHON = "' + $pyPath + '"' + Write-Utf8NoBom -Path $envPath -Content $envContent + + Info ("{0} $envPath" -f (T "Environment file generated:" "已生成环境文件:")) +} + +Title (T "🦣 OpenClaw + OpenViking Installer" "🦣 OpenClaw + OpenViking 一键安装") +Write-Host "" + +Validate-Environment +Check-OpenClaw +Install-OpenViking +$serverPort = Configure-OvConf +Download-Plugin +Configure-OpenClawPlugin -ServerPort $serverPort +Write-OpenVikingEnv + +Write-Host "" +Title "═══════════════════════════════════════════════════════════" +Title (" {0}" -f (T "Installation complete!" "安装完成!")) +Title "═══════════════════════════════════════════════════════════" +Write-Host "" +Info (T "Run these commands to start OpenClaw + OpenViking:" "请按以下命令启动 OpenClaw + OpenViking:") +Write-Host " 1) openclaw --version" +Write-Host " 2) openclaw onboard" +Write-Host " 3) . `"$OpenClawDir\openviking.env.ps1`"; openclaw gateway" +Write-Host " 4) openclaw status" +Write-Host "" +Info ("{0} $OpenVikingDir\ov.conf" -f (T "You can edit the config freely:" "你可以按需自由修改配置文件:")) +Write-Host "" diff --git a/examples/openclaw-memory-plugin/install.sh b/examples/openclaw-memory-plugin/install.sh new file mode 100644 index 00000000..83ee1051 --- /dev/null +++ b/examples/openclaw-memory-plugin/install.sh @@ -0,0 +1,440 @@ +#!/bin/bash +# +# OpenClaw + OpenViking one-click installer +# Usage: curl -fsSL https://raw.githubusercontent.com/volcengine/OpenViking/main/examples/openclaw-memory-plugin/install.sh | bash +# +# Environment variables: +# REPO=owner/repo - GitHub repository (default: volcengine/OpenViking) +# BRANCH=branch - Git branch/tag/commit (default: main) +# OPENVIKING_INSTALL_YES=1 - non-interactive mode (same as -y) +# SKIP_OPENCLAW=1 - skip OpenClaw check +# SKIP_OPENVIKING=1 - skip OpenViking installation +# NPM_REGISTRY=url - npm registry (default: https://registry.npmmirror.com) +# PIP_INDEX_URL=url - pip index URL (default: https://pypi.tuna.tsinghua.edu.cn/simple) +# OPENVIKING_VLM_API_KEY - VLM model API key (optional) +# OPENVIKING_EMBEDDING_API_KEY - Embedding model API key (optional) +# OPENVIKING_ARK_API_KEY - legacy fallback for both keys +# + +set -e + +REPO="${REPO:-volcengine/OpenViking}" +BRANCH="${BRANCH:-main}" +INSTALL_YES="${OPENVIKING_INSTALL_YES:-0}" +SKIP_OC="${SKIP_OPENCLAW:-0}" +SKIP_OV="${SKIP_OPENVIKING:-0}" +NPM_REGISTRY="${NPM_REGISTRY:-https://registry.npmmirror.com}" +PIP_INDEX_URL="${PIP_INDEX_URL:-https://pypi.tuna.tsinghua.edu.cn/simple}" +HOME_DIR="${HOME:-$USERPROFILE}" +OPENCLAW_DIR="${HOME_DIR}/.openclaw" +OPENVIKING_DIR="${HOME_DIR}/.openviking" +PLUGIN_DEST="${OPENCLAW_DIR}/extensions/memory-openviking" +DEFAULT_SERVER_PORT=1933 +DEFAULT_AGFS_PORT=1833 +DEFAULT_VLM_MODEL="doubao-seed-1-8-251228" +DEFAULT_EMBED_MODEL="doubao-embedding-vision-250615" +SELECTED_SERVER_PORT="${DEFAULT_SERVER_PORT}" +LANG_UI="en" + +# Parse args (supports curl | bash -s -- ...) +for arg in "$@"; do + [[ "$arg" == "-y" || "$arg" == "--yes" ]] && INSTALL_YES="1" + [[ "$arg" == "--zh" ]] && LANG_UI="zh" + [[ "$arg" == "-h" || "$arg" == "--help" ]] && { + echo "Usage: curl -fsSL | bash [-s -- -y --zh]" + echo "" + echo "Options:" + echo " -y, --yes Non-interactive mode" + echo " --zh Chinese prompts" + echo " -h, --help Show this help" + echo "" + echo "Env vars: REPO, BRANCH, OPENVIKING_INSTALL_YES, SKIP_OPENCLAW, SKIP_OPENVIKING, NPM_REGISTRY, PIP_INDEX_URL" + exit 0 + } +done + +tr() { + local en="$1" + local zh="$2" + if [[ "$LANG_UI" == "zh" ]]; then + echo "$zh" + else + echo "$en" + fi +} + +# Prefer interactive mode. Even with curl | bash, try reading from /dev/tty. +# Fall back to defaults only when no interactive TTY is available. +if [[ ! -t 0 && "$INSTALL_YES" != "1" ]]; then + if [[ ! -r /dev/tty ]]; then + INSTALL_YES="1" + echo "[WARN] $(tr "No interactive TTY detected. Falling back to defaults (-y)." "未检测到可交互终端,自动切换为默认配置模式(等同于 -y)")" + else + echo "[INFO] $(tr "Pipeline execution detected. Interactive prompts will use /dev/tty." "检测到管道执行,将通过 /dev/tty 进入交互配置")" + fi +fi + +# 颜色与输出 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BOLD='\033[1m' +NC='\033[0m' + +info() { echo -e "${GREEN}[INFO]${NC} $1"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } +err() { echo -e "${RED}[ERROR]${NC} $1"; } +bold() { echo -e "${BOLD}$1${NC}"; } + +# Detect OS +detect_os() { + case "$(uname -s)" in + Linux*) OS="linux";; + Darwin*) OS="macos";; + CYGWIN*|MINGW*|MSYS*) OS="windows";; + *) OS="unknown";; + esac + if [[ "$OS" == "windows" ]]; then + err "$(tr "Windows is not supported by this installer yet. Please follow the docs for manual setup." "Windows 暂不支持此一键安装脚本,请参考文档手动安装。")" + exit 1 + fi +} + +# Detect Linux distro +detect_distro() { + DISTRO="unknown" + if [[ -f /etc/os-release ]]; then + . /etc/os-release 2>/dev/null || true + case "${ID:-}" in + ubuntu|debian|linuxmint) DISTRO="debian";; + fedora|rhel|centos|rocky|almalinux|openeuler) DISTRO="rhel";; + esac + fi + if command -v apt &>/dev/null; then + DISTRO="debian" + elif command -v dnf &>/dev/null || command -v yum &>/dev/null; then + DISTRO="rhel" + fi +} + +# ─── Environment checks ─── + +check_python() { + local py="${OPENVIKING_PYTHON:-python3}" + local out + if ! out=$("$py" -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" 2>/dev/null); then + echo "fail|$py|$(tr "Python not found. Install Python >= 3.10." "Python 未找到,请安装 Python >= 3.10")" + return 1 + fi + local major minor + IFS=. read -r major minor <<< "$out" + if [[ "$major" -lt 3 ]] || [[ "$major" -eq 3 && "$minor" -lt 10 ]]; then + echo "fail|$out|$(tr "Python $out is too old. Need >= 3.10." "Python 版本 $out 过低,需要 >= 3.10")" + return 1 + fi + echo "ok|$out|$py" + return 0 +} + +check_node() { + local out + if ! out=$(node -v 2>/dev/null); then + echo "fail||$(tr "Node.js not found. Install Node.js >= 22." "Node.js 未找到,请安装 Node.js >= 22")" + return 1 + fi + local v="${out#v}" + local major + major="${v%%.*}" + if [[ -z "$major" ]] || [[ "$major" -lt 22 ]]; then + echo "fail|$out|$(tr "Node.js $out is too old. Need >= 22." "Node.js 版本 $out 过低,需要 >= 22")" + return 1 + fi + echo "ok|$out|node" + return 0 +} + +# Print guidance for missing dependencies +print_install_hints() { + local missing=("$@") + bold "\n═══════════════════════════════════════════════════════════" + bold " $(tr "Environment check failed. Install missing dependencies first:" "环境校验未通过,请先安装以下缺失组件:")" + bold "═══════════════════════════════════════════════════════════\n" + + for item in "${missing[@]}"; do + local name="${item%%|*}" + local rest="${item#*|}" + err "$(tr "Missing: $name" "缺失: $name")" + [[ -n "$rest" ]] && echo " $rest" + echo "" + done + + detect_distro + echo "$(tr "Based on your system ($DISTRO), you can run:" "根据你的系统 ($DISTRO),可执行以下命令安装:")" + echo "" + + if printf '%s\n' "${missing[@]}" | grep -q "Python"; then + echo " # $(tr "Install Python 3.10+ (pyenv recommended)" "安装 Python 3.10+(推荐 pyenv)")" + echo " curl https://pyenv.run | bash" + echo " export PATH=\"\$HOME/.pyenv/bin:\$PATH\"" + echo " eval \"\$(pyenv init -)\"" + echo " pyenv install 3.11.12" + echo " pyenv global 3.11.12" + echo " python3 --version # $(tr "verify >= 3.10" "确认 >= 3.10")" + echo "" + fi + + if printf '%s\n' "${missing[@]}" | grep -q "Node"; then + echo " # $(tr "Install Node.js 22+ (nvm)" "安装 Node.js 22+(nvm)")" + echo " curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash" + echo " source ~/.bashrc" + echo " nvm install 22" + echo " nvm use 22" + echo " node -v # $(tr "verify >= v22" "确认 >= v22")" + echo "" + fi + + bold "$(tr "After installation, rerun this script." "安装完成后,请重新运行本脚本。")" + bold "$(tr "See details: https://github.com/${REPO}/blob/${BRANCH}/examples/openclaw-memory-plugin/INSTALL.md" "详细说明见: https://github.com/${REPO}/blob/${BRANCH}/examples/openclaw-memory-plugin/INSTALL-ZH.md")" + echo "" + exit 1 +} + +# Validate environment +validate_environment() { + info "$(tr "Checking OpenViking runtime environment..." "正在校验 OpenViking 运行环境...")" + echo "" + + local missing=() + local r + + r=$(check_python) || missing+=("Python 3.10+ | $(echo "$r" | cut -d'|' -f3)") + if [[ "${r%%|*}" == "ok" ]]; then + info " Python: $(echo "$r" | cut -d'|' -f2) ✓" + fi + + r=$(check_node) || missing+=("Node.js 22+ | $(echo "$r" | cut -d'|' -f3)") + if [[ "${r%%|*}" == "ok" ]]; then + info " Node.js: $(echo "$r" | cut -d'|' -f2) ✓" + fi + + if [[ ${#missing[@]} -gt 0 ]]; then + echo "" + print_install_hints "${missing[@]}" + fi + + echo "" + info "$(tr "Environment check passed ✓" "环境校验通过 ✓")" + echo "" +} + +# ─── Install flow ─── + +install_openclaw() { + if [[ "$SKIP_OC" == "1" ]]; then + info "$(tr "Skipping OpenClaw check (SKIP_OPENCLAW=1)" "跳过 OpenClaw 校验 (SKIP_OPENCLAW=1)")" + return 0 + fi + info "$(tr "Checking OpenClaw..." "正在校验 OpenClaw...")" + if command -v openclaw >/dev/null 2>&1; then + info "$(tr "OpenClaw detected ✓" "OpenClaw 已安装 ✓")" + return 0 + fi + + err "$(tr "OpenClaw not found. Install it manually, then rerun this script." "未检测到 OpenClaw,请先手动安装后再执行本脚本")" + echo "" + echo "$(tr "Recommended command:" "推荐命令:")" + echo " npm install -g openclaw --registry ${NPM_REGISTRY}" + echo "" + echo "$(tr "If npm global install fails, install Node via nvm and retry." "如 npm 全局安装失败,建议先用 nvm 安装 Node 后再执行上述命令。")" + echo "$(tr "After installation, run:" "安装完成后,运行:")" + echo " openclaw --version" + echo " openclaw onboard" + echo "" + exit 1 +} + +install_openviking() { + if [[ "$SKIP_OV" == "1" ]]; then + info "$(tr "Skipping OpenViking install (SKIP_OPENVIKING=1)" "跳过 OpenViking 安装 (SKIP_OPENVIKING=1)")" + return 0 + fi + info "$(tr "Installing OpenViking from PyPI..." "正在安装 OpenViking (PyPI)...")" + info "$(tr "Using pip index: ${PIP_INDEX_URL}" "使用 pip 镜像源: ${PIP_INDEX_URL}")" + python3 -m pip install --upgrade pip -q -i "${PIP_INDEX_URL}" + python3 -m pip install openviking -i "${PIP_INDEX_URL}" || { + err "$(tr "OpenViking install failed. Check Python version (>=3.10) and pip." "OpenViking 安装失败,请检查 Python 版本 (需 >= 3.10) 及 pip")" + exit 1 + } + info "$(tr "OpenViking installed ✓" "OpenViking 安装完成 ✓")" +} + +configure_openviking_conf() { + mkdir -p "${OPENVIKING_DIR}" + + local workspace="${OPENVIKING_DIR}/data" + local server_port="${DEFAULT_SERVER_PORT}" + local agfs_port="${DEFAULT_AGFS_PORT}" + local vlm_model="${DEFAULT_VLM_MODEL}" + local embedding_model="${DEFAULT_EMBED_MODEL}" + local vlm_api_key="${OPENVIKING_VLM_API_KEY:-${OPENVIKING_ARK_API_KEY:-}}" + local embedding_api_key="${OPENVIKING_EMBEDDING_API_KEY:-${OPENVIKING_ARK_API_KEY:-}}" + local conf_path="${OPENVIKING_DIR}/ov.conf" + local vlm_api_json="null" + local embedding_api_json="null" + + if [[ "$INSTALL_YES" != "1" ]]; then + echo "" + read -r -p "$(tr "OpenViking workspace path [${workspace}]: " "OpenViking 数据目录 [${workspace}]: ")" _workspace < /dev/tty || true + read -r -p "$(tr "OpenViking HTTP port [${server_port}]: " "OpenViking HTTP 端口 [${server_port}]: ")" _server_port < /dev/tty || true + read -r -p "$(tr "AGFS port [${agfs_port}]: " "AGFS 端口 [${agfs_port}]: ")" _agfs_port < /dev/tty || true + read -r -p "$(tr "VLM model [${vlm_model}]: " "VLM 模型 [${vlm_model}]: ")" _vlm_model < /dev/tty || true + read -r -p "$(tr "Embedding model [${embedding_model}]: " "Embedding 模型 [${embedding_model}]: ")" _embedding_model < /dev/tty || true + echo "$(tr "VLM and Embedding API keys can differ. You can leave either empty and edit ov.conf later." "说明:VLM 与 Embedding 的 API Key 可能不同,可分别填写;留空后续可在 ov.conf 修改。")" + read -r -p "$(tr "VLM API key (optional): " "VLM API Key(可留空): ")" _vlm_api_key < /dev/tty || true + read -r -p "$(tr "Embedding API key (optional): " "Embedding API Key(可留空): ")" _embedding_api_key < /dev/tty || true + + workspace="${_workspace:-$workspace}" + server_port="${_server_port:-$server_port}" + agfs_port="${_agfs_port:-$agfs_port}" + vlm_model="${_vlm_model:-$vlm_model}" + embedding_model="${_embedding_model:-$embedding_model}" + vlm_api_key="${_vlm_api_key:-$vlm_api_key}" + embedding_api_key="${_embedding_api_key:-$embedding_api_key}" + fi + + if [[ -n "${vlm_api_key}" ]]; then + vlm_api_json="\"${vlm_api_key}\"" + fi + if [[ -n "${embedding_api_key}" ]]; then + embedding_api_json="\"${embedding_api_key}\"" + fi + + mkdir -p "${workspace}" + cat > "${conf_path}" < "${OPENCLAW_DIR}/openviking.env" <