diff --git a/openclaw/docker/.env.example b/openclaw/docker/.env.example new file mode 100644 index 000000000..e5b4b41ce --- /dev/null +++ b/openclaw/docker/.env.example @@ -0,0 +1,26 @@ +# OpenClaw + Mem0 Docker Setup +# Copy this file to .env and fill in your values: +# cp .env.example .env + +# ── Required (one of these depending on mode) ─────────────────────────────── + +# Platform mode: Mem0 Cloud API key from https://app.mem0.ai +MEM0_API_KEY= + +# OSS mode: OpenAI key for embeddings, LLM, and the OpenClaw agent +# Platform mode: only needed if you want a specific LLM for the agent itself +OPENAI_API_KEY= + +# ── Optional ───────────────────────────────────────────────────────────────── + +# User ID for memory scoping (defaults to your OS username) +MEM0_USER_ID= + +# Web UI port (default: 18789) +OPENCLAW_PORT=18789 + +# Config directory on host (default: ~/.openclaw) +# OPENCLAW_CONFIG_DIR=~/.openclaw + +# Timezone (default: UTC) +TZ=UTC diff --git a/openclaw/docker/README.md b/openclaw/docker/README.md new file mode 100644 index 000000000..d02569001 --- /dev/null +++ b/openclaw/docker/README.md @@ -0,0 +1,102 @@ +# OpenClaw + Mem0 One-Click Docker Setup + +Get a working OpenClaw agent with Mem0 persistent memory in under 2 minutes. + +## Prerequisites + +- [Docker Desktop](https://www.docker.com/products/docker-desktop/) or Docker Engine with Compose v2 +- One of: + - **Platform mode**: A Mem0 API key from [app.mem0.ai](https://app.mem0.ai) + - **OSS mode**: An OpenAI API key + +## Quick Start + +```bash +git clone https://github.com/mem0ai/mem0 && cd mem0/openclaw/docker +./setup.sh +``` + +The setup script will: +1. Ask you to choose Platform or Open-Source mode +2. Prompt for your API key(s) +3. Start the containers automatically + +Then open **http://localhost:18789** to start chatting. + +## What Gets Deployed + +### Platform Mode +``` +┌─────────────────────────┐ +│ OpenClaw (port 18789) │ +│ + Mem0 Plugin │──→ Mem0 Cloud (app.mem0.ai) +└─────────────────────────┘ +``` + +### OSS Mode +``` +┌─────────────────────────┐ ┌─────────────────┐ +│ OpenClaw (port 18789) │ │ Qdrant │ +│ + Mem0 Plugin │────→│ (port 6333) │ +└─────────────────────────┘ │ Vector Storage │ + └─────────────────┘ +``` + +## Modes Explained + +| Feature | Platform (Mem0 Cloud) | Open-Source (Self-Hosted) | +|---------|----------------------|--------------------------| +| Setup | API key only | API key + Qdrant container | +| Vector storage | Managed by Mem0 | Local Qdrant | +| LLM for extraction | Managed by Mem0 | Your OpenAI key | +| Graph memory | Supported | Not yet | +| Dashboard | app.mem0.ai | CLI commands | + +## Configuration + +After setup, your config lives at `~/.openclaw/openclaw.json`. You can edit it directly. + +Key settings in the `openclaw-mem0` plugin section: + +| Setting | Default | Description | +|---------|---------|-------------| +| `autoCapture` | `true` | Auto-store conversation facts | +| `autoRecall` | `true` | Auto-retrieve relevant memories | +| `searchThreshold` | `0.5` | Minimum similarity score (0-1) | +| `topK` | `5` | Max memories to retrieve | + +To change the port or other Docker settings, edit `.env` in this directory. + +## Troubleshooting + +**Port 18789 already in use** +```bash +lsof -i :18789 +# Kill the process or change OPENCLAW_PORT in .env +``` + +**API key errors** +- Platform: Verify your key at [app.mem0.ai](https://app.mem0.ai) +- OSS: Ensure `OPENAI_API_KEY` is valid and has credits + +**Containers won't start** +```bash +docker compose logs +# or for OSS mode: +docker compose -f docker-compose.yml -f docker-compose.oss.yml logs +``` + +**Docker not found** +Install [Docker Desktop](https://www.docker.com/products/docker-desktop/) or follow the [engine install guide](https://docs.docker.com/engine/install/). + +**Plugin not loading** +```bash +# Reinstall the plugin manually +docker compose run --rm openclaw openclaw plugins install @mem0/openclaw-mem0 +``` + +## Links + +- [Mem0 Documentation](https://docs.mem0.ai) +- [Mem0 OpenClaw Plugin](https://github.com/mem0ai/mem0/tree/main/openclaw) +- [OpenClaw Documentation](https://openclaw.com/docs) diff --git a/openclaw/docker/config/openclaw.oss.json b/openclaw/docker/config/openclaw.oss.json new file mode 100644 index 000000000..e93624fe4 --- /dev/null +++ b/openclaw/docker/config/openclaw.oss.json @@ -0,0 +1,21 @@ +{ + "plugins": { + "openclaw-mem0": { + "mode": "open-source", + "userId": "__MEM0_USER_ID__", + "autoCapture": true, + "autoRecall": true, + "searchThreshold": 0.5, + "topK": 5, + "oss": { + "vectorStore": { + "provider": "qdrant", + "config": { + "host": "qdrant", + "port": 6333 + } + } + } + } + } +} diff --git a/openclaw/docker/config/openclaw.platform.json b/openclaw/docker/config/openclaw.platform.json new file mode 100644 index 000000000..69fd8e3df --- /dev/null +++ b/openclaw/docker/config/openclaw.platform.json @@ -0,0 +1,13 @@ +{ + "plugins": { + "openclaw-mem0": { + "mode": "platform", + "apiKey": "${MEM0_API_KEY}", + "userId": "__MEM0_USER_ID__", + "autoCapture": true, + "autoRecall": true, + "searchThreshold": 0.5, + "topK": 5 + } + } +} diff --git a/openclaw/docker/docker-compose.oss.yml b/openclaw/docker/docker-compose.oss.yml new file mode 100644 index 000000000..4455171f9 --- /dev/null +++ b/openclaw/docker/docker-compose.oss.yml @@ -0,0 +1,23 @@ +services: + qdrant: + image: qdrant/qdrant:latest + volumes: + - qdrant_data:/qdrant/storage + # No host port exposure — only accessible via Docker network (openclaw → qdrant:6333) + # Uncomment the next line for debugging: ports: ["127.0.0.1:6333:6333"] + expose: + - "6333" + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "bash -c ':> /dev/tcp/localhost/6333' || exit 1"] + interval: 10s + timeout: 3s + retries: 5 + + openclaw: + depends_on: + qdrant: + condition: service_healthy + +volumes: + qdrant_data: diff --git a/openclaw/docker/docker-compose.yml b/openclaw/docker/docker-compose.yml new file mode 100644 index 000000000..2ad5a1a11 --- /dev/null +++ b/openclaw/docker/docker-compose.yml @@ -0,0 +1,22 @@ +services: + openclaw: + image: ghcr.io/openclaw/openclaw:latest + environment: + HOME: /home/node + TERM: xterm-256color + OPENAI_API_KEY: ${OPENAI_API_KEY:-} + MEM0_API_KEY: ${MEM0_API_KEY:-} + TZ: ${TZ:-UTC} + volumes: + - ${OPENCLAW_CONFIG_DIR:-~/.openclaw}:/home/node/.openclaw + ports: + # Bound to localhost only for security — change to "18789:18789" for LAN access + - "127.0.0.1:${OPENCLAW_PORT:-18789}:18789" + restart: unless-stopped + command: ["node", "dist/index.js", "gateway", "--bind", "lan", "--port", "18789"] + healthcheck: + test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:18789/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"] + interval: 15s + timeout: 5s + retries: 5 + start_period: 30s diff --git a/openclaw/docker/setup.sh b/openclaw/docker/setup.sh new file mode 100755 index 000000000..1cf467e55 --- /dev/null +++ b/openclaw/docker/setup.sh @@ -0,0 +1,268 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ============================================================================ +# OpenClaw + Mem0 One-Click Setup +# ============================================================================ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +OPENCLAW_CONFIG_DIR="${OPENCLAW_CONFIG_DIR:-$HOME/.openclaw}" +OPENCLAW_PORT="${OPENCLAW_PORT:-18789}" + +# Colors (disabled if not a terminal) +if [ -t 1 ]; then + RED='\033[0;31m' + GREEN='\033[0;32m' + YELLOW='\033[1;33m' + BLUE='\033[0;34m' + BOLD='\033[1m' + NC='\033[0m' +else + RED='' GREEN='' YELLOW='' BLUE='' BOLD='' NC='' +fi + +info() { echo -e "${BLUE}ℹ${NC} $*"; } +ok() { echo -e "${GREEN}✔${NC} $*"; } +warn() { echo -e "${YELLOW}⚠${NC} $*"; } +error() { echo -e "${RED}✖${NC} $*" >&2; } +header() { echo -e "\n${BOLD}$*${NC}"; } + +# ============================================================================ +# Step 1: Check prerequisites +# ============================================================================ + +header "Checking prerequisites..." + +if ! command -v docker &>/dev/null; then + error "Docker is not installed." + echo " Install Docker Desktop: https://www.docker.com/products/docker-desktop/" + echo " Or Docker Engine: https://docs.docker.com/engine/install/" + exit 1 +fi + +if ! docker compose version &>/dev/null; then + error "Docker Compose v2 is not available." + echo " Ensure you have Docker Compose v2 (comes with Docker Desktop)." + echo " Docs: https://docs.docker.com/compose/install/" + exit 1 +fi + +if ! docker info &>/dev/null 2>&1; then + error "Docker daemon is not running. Please start Docker and try again." + exit 1 +fi + +ok "Docker and Docker Compose are available." + +# Check for port conflict +if command -v lsof &>/dev/null && lsof -i :"$OPENCLAW_PORT" &>/dev/null; then + warn "Port $OPENCLAW_PORT is already in use." + echo " Check what's using it: lsof -i :$OPENCLAW_PORT" + echo " Or set a different port: OPENCLAW_PORT=18790 ./setup.sh" + exit 1 +fi + +# ============================================================================ +# Step 2: Check for existing config +# ============================================================================ + +if [ -f "$OPENCLAW_CONFIG_DIR/openclaw.json" ]; then + warn "Existing config found at $OPENCLAW_CONFIG_DIR/openclaw.json" + read -rp " Overwrite with new config? [y/N] " overwrite + if [[ ! "$overwrite" =~ ^[Yy]$ ]]; then + info "Keeping existing config. Skipping config generation." + SKIP_CONFIG=true + else + SKIP_CONFIG=false + fi +else + SKIP_CONFIG=false +fi + +# ============================================================================ +# Step 3: Choose mode +# ============================================================================ + +header "Choose your setup mode:" +echo "" +echo " 1) Platform — Uses Mem0 Cloud (managed storage, dashboard at app.mem0.ai)" +echo " 2) OSS — Self-hosted with local Qdrant (fully offline-capable)" +echo "" + +while true; do + read -rp "Enter 1 or 2: " mode_choice + case "$mode_choice" in + 1) MODE="platform"; break ;; + 2) MODE="oss"; break ;; + *) echo " Please enter 1 or 2." ;; + esac +done + +ok "Mode: $MODE" + +# ============================================================================ +# Step 4: Collect API keys +# ============================================================================ + +header "API Key Configuration" + +MEM0_API_KEY="" +OPENAI_API_KEY="" + +if [ "$MODE" = "platform" ]; then + echo " Platform mode requires a Mem0 API key from https://app.mem0.ai" + echo "" + while true; do + read -rp " Mem0 API Key: " MEM0_API_KEY + if [ -n "$MEM0_API_KEY" ]; then break; fi + warn "API key is required for platform mode." + done + + echo "" + read -rp " OpenAI API Key (optional, for agent LLM — press Enter to skip): " OPENAI_API_KEY +else + echo " OSS mode requires an OpenAI API key for embeddings, LLM, and the agent." + echo "" + while true; do + read -rp " OpenAI API Key: " OPENAI_API_KEY + if [ -n "$OPENAI_API_KEY" ]; then break; fi + warn "API key is required for OSS mode." + done +fi + +ok "API keys configured." + +# ============================================================================ +# Step 5: User ID +# ============================================================================ + +DEFAULT_USER_ID="$(whoami)" +read -rp " User ID for memory scoping [${DEFAULT_USER_ID}]: " MEM0_USER_ID +MEM0_USER_ID="${MEM0_USER_ID:-$DEFAULT_USER_ID}" +ok "User ID: $MEM0_USER_ID" + +# ============================================================================ +# Step 6: Generate .env +# ============================================================================ + +header "Generating configuration..." + +cat > "$SCRIPT_DIR/.env" < "$OPENCLAW_CONFIG_DIR/openclaw.json" + + ok "Created $OPENCLAW_CONFIG_DIR/openclaw.json" +fi + +# ============================================================================ +# Step 8: Install Mem0 plugin +# ============================================================================ + +header "Installing Mem0 plugin..." + +COMPOSE_CMD="docker compose" +if [ "$MODE" = "oss" ]; then + COMPOSE_CMD="docker compose -f docker-compose.yml -f docker-compose.oss.yml" +fi + +cd "$SCRIPT_DIR" + +if $COMPOSE_CMD run --rm openclaw openclaw plugins install @mem0/openclaw-mem0 2>/dev/null; then + ok "Mem0 plugin installed." +else + warn "Plugin install via Docker failed. This may be normal on first run." + echo " You can install manually later:" + echo " $COMPOSE_CMD run --rm openclaw openclaw plugins install @mem0/openclaw-mem0" +fi + +# ============================================================================ +# Step 9: Copy onboarding skill +# ============================================================================ + +SKILL_DIR="$OPENCLAW_CONFIG_DIR/workspace/skills/mem0-onboarding" +mkdir -p "$SKILL_DIR" +cp "$SCRIPT_DIR/skills/mem0-onboarding/SKILL.md" "$SKILL_DIR/SKILL.md" +ok "Onboarding skill installed." + +# ============================================================================ +# Step 10: Start containers +# ============================================================================ + +header "Starting OpenClaw..." + +$COMPOSE_CMD up -d + +# ============================================================================ +# Step 11: Health check +# ============================================================================ + +info "Waiting for OpenClaw to be ready..." + +HEALTH_URL="http://127.0.0.1:${OPENCLAW_PORT}/healthz" +TIMEOUT=90 +ELAPSED=0 +INTERVAL=3 + +while [ $ELAPSED -lt $TIMEOUT ]; do + if curl -sf "$HEALTH_URL" &>/dev/null; then + echo "" + ok "OpenClaw is ready!" + break + fi + printf "." + sleep $INTERVAL + ELAPSED=$((ELAPSED + INTERVAL)) +done + +if [ $ELAPSED -ge $TIMEOUT ]; then + echo "" + warn "Health check timed out after ${TIMEOUT}s." + echo " OpenClaw may still be starting. Check logs:" + echo " cd $SCRIPT_DIR && $COMPOSE_CMD logs -f" + exit 1 +fi + +# ============================================================================ +# Done! +# ============================================================================ + +echo "" +header "Setup complete!" +echo "" +echo " Open the web UI: ${BOLD}http://localhost:${OPENCLAW_PORT}${NC}" +echo " Mode: ${MODE}" +echo " User ID: ${MEM0_USER_ID}" +echo " Config: ${OPENCLAW_CONFIG_DIR}/openclaw.json" +echo "" +echo " ${BLUE}Tip:${NC} Your first chat will trigger the Mem0 onboarding skill." +echo " Try telling the agent something about yourself to test memory!" +echo "" +echo " Useful commands:" +echo " View logs: cd $SCRIPT_DIR && $COMPOSE_CMD logs -f" +echo " Stop: cd $SCRIPT_DIR && $COMPOSE_CMD down" +echo " Restart: cd $SCRIPT_DIR && $COMPOSE_CMD restart" +echo "" diff --git a/openclaw/docker/skills/mem0-onboarding/SKILL.md b/openclaw/docker/skills/mem0-onboarding/SKILL.md new file mode 100644 index 000000000..937610cce --- /dev/null +++ b/openclaw/docker/skills/mem0-onboarding/SKILL.md @@ -0,0 +1,37 @@ +# Mem0 Memory Onboarding + +Welcome! This OpenClaw agent has **persistent memory** powered by Mem0. It remembers things across sessions so you never have to repeat yourself. + +## How It Works + +- **Auto-Capture**: After each conversation turn, important facts are automatically extracted and stored. +- **Auto-Recall**: Before each turn, relevant memories are retrieved to give context. + +## Quick Demo — Try These 3 Steps + +### Step 1: Tell me something about yourself +Share a preference, a project you're working on, or a fact about yourself. For example: +> "I prefer TypeScript over JavaScript and I'm building a SaaS app called Acme." + +### Step 2: Confirm storage +After you share, I'll confirm that the information was captured. You can also check with: +> "What do you remember about me?" + +### Step 3: Verify persistence +Start a **new session** (close this chat and open a new one), then ask: +> "What do you know about me?" + +Your memories carry over — that's the magic of persistent memory. + +## Power User Commands + +If your OpenClaw setup includes the Mem0 CLI commands: + +- `openclaw mem0 search "query"` — Search your memories +- `openclaw mem0 stats` — View memory statistics +- `openclaw mem0 list` — List recent memories + +## Learn More + +- [Mem0 Documentation](https://docs.mem0.ai) +- [OpenClaw Plugin README](https://github.com/mem0ai/mem0/tree/main/openclaw) diff --git a/openclaw/docker/test-setup.sh b/openclaw/docker/test-setup.sh new file mode 100755 index 000000000..093ec7a49 --- /dev/null +++ b/openclaw/docker/test-setup.sh @@ -0,0 +1,280 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ============================================================================ +# Test script for OpenClaw + Mem0 Docker setup +# Validates everything works without needing real API keys +# ============================================================================ + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PASS=0 +FAIL=0 +TESTS=() + +pass() { PASS=$((PASS + 1)); TESTS+=("✔ $1"); echo " ✔ $1"; } +fail() { FAIL=$((FAIL + 1)); TESTS+=("✖ $1: $2"); echo " ✖ $1: $2"; } + +echo "" +echo "═══════════════════════════════════════════════════" +echo " OpenClaw + Mem0 Docker Setup — Test Suite" +echo "═══════════════════════════════════════════════════" + +# ── Prerequisites ──────────────────────────────────────────────────────────── + +echo "" +echo "▸ Prerequisites" + +if command -v docker &>/dev/null; then + pass "Docker installed" +else + fail "Docker installed" "docker not found" +fi + +if docker compose version &>/dev/null; then + pass "Docker Compose v2 available" +else + fail "Docker Compose v2 available" "docker compose not found" +fi + +if docker info &>/dev/null 2>&1; then + pass "Docker daemon running" +else + fail "Docker daemon running" "daemon not responding" +fi + +# ── File structure ─────────────────────────────────────────────────────────── + +echo "" +echo "▸ File structure" + +EXPECTED_FILES=( + "setup.sh" + "docker-compose.yml" + "docker-compose.oss.yml" + ".env.example" + "config/openclaw.platform.json" + "config/openclaw.oss.json" + "skills/mem0-onboarding/SKILL.md" + "README.md" +) + +for f in "${EXPECTED_FILES[@]}"; do + if [ -f "$SCRIPT_DIR/$f" ]; then + pass "File exists: $f" + else + fail "File exists: $f" "missing" + fi +done + +if [ -x "$SCRIPT_DIR/setup.sh" ]; then + pass "setup.sh is executable" +else + fail "setup.sh is executable" "not executable" +fi + +# ── Bash syntax ────────────────────────────────────────────────────────────── + +echo "" +echo "▸ Script syntax" + +if bash -n "$SCRIPT_DIR/setup.sh" 2>&1; then + pass "setup.sh syntax valid" +else + fail "setup.sh syntax valid" "syntax errors" +fi + +# ── JSON validity ──────────────────────────────────────────────────────────── + +echo "" +echo "▸ JSON configs" + +for json_file in config/openclaw.platform.json config/openclaw.oss.json; do + if python3 -c "import json; json.load(open('$SCRIPT_DIR/$json_file'))" 2>/dev/null; then + pass "$json_file is valid JSON" + else + fail "$json_file is valid JSON" "parse error" + fi +done + +# Check platform config has expected fields +if python3 -c " +import json, sys +c = json.load(open('$SCRIPT_DIR/config/openclaw.platform.json')) +p = c['plugins']['openclaw-mem0'] +assert p['mode'] == 'platform', 'wrong mode' +assert p['apiKey'] == '\${MEM0_API_KEY}', 'apiKey not using env var' +assert p['userId'] == '__MEM0_USER_ID__', 'userId placeholder missing' +assert p['autoCapture'] is True +assert p['autoRecall'] is True +" 2>/dev/null; then + pass "Platform config has correct structure" +else + fail "Platform config has correct structure" "missing or wrong fields" +fi + +# Check OSS config has Qdrant vector store +if python3 -c " +import json +c = json.load(open('$SCRIPT_DIR/config/openclaw.oss.json')) +p = c['plugins']['openclaw-mem0'] +assert p['mode'] == 'open-source', 'wrong mode' +vs = p['oss']['vectorStore'] +assert vs['provider'] == 'qdrant' +assert vs['config']['host'] == 'qdrant' +assert vs['config']['port'] == 6333 +" 2>/dev/null; then + pass "OSS config has Qdrant vector store" +else + fail "OSS config has Qdrant vector store" "missing or wrong fields" +fi + +# ── Docker Compose validation ──────────────────────────────────────────────── + +echo "" +echo "▸ Docker Compose validation" + +cd "$SCRIPT_DIR" + +# Create a temporary .env so compose can resolve variables +TEMP_ENV=false +if [ ! -f .env ]; then + cat > .env <<'EOF' +MEM0_API_KEY=test-key-for-validation +OPENAI_API_KEY=test-key-for-validation +MEM0_USER_ID=testuser +OPENCLAW_PORT=18789 +OPENCLAW_CONFIG_DIR=/tmp/openclaw-test +TZ=UTC +EOF + TEMP_ENV=true +fi + +if docker compose config --quiet 2>/dev/null; then + pass "docker-compose.yml is valid" +else + fail "docker-compose.yml is valid" "$(docker compose config 2>&1 | tail -1)" +fi + +if docker compose -f docker-compose.yml -f docker-compose.oss.yml config --quiet 2>/dev/null; then + pass "docker-compose.oss.yml override is valid" +else + fail "docker-compose.oss.yml override is valid" "$(docker compose -f docker-compose.yml -f docker-compose.oss.yml config 2>&1 | tail -1)" +fi + +# Verify services are correctly defined +PLATFORM_SERVICES=$(docker compose config --services 2>/dev/null) +if echo "$PLATFORM_SERVICES" | grep -q "openclaw"; then + pass "Platform mode defines 'openclaw' service" +else + fail "Platform mode defines 'openclaw' service" "service not found" +fi + +OSS_SERVICES=$(docker compose -f docker-compose.yml -f docker-compose.oss.yml config --services 2>/dev/null) +if echo "$OSS_SERVICES" | grep -q "qdrant"; then + pass "OSS mode adds 'qdrant' service" +else + fail "OSS mode adds 'qdrant' service" "service not found" +fi + +# Verify port binding is localhost-only +PORT_CONFIG=$(docker compose config 2>/dev/null | grep -A5 "published" | head -10) +if docker compose config 2>/dev/null | grep -q "127.0.0.1"; then + pass "Port is bound to 127.0.0.1 (localhost only)" +else + fail "Port is bound to 127.0.0.1 (localhost only)" "binding not found" +fi + +# Verify healthcheck is defined +if docker compose config 2>/dev/null | grep -q "healthz"; then + pass "Healthcheck is configured" +else + fail "Healthcheck is configured" "healthcheck not found" +fi + +# ── Docker image pull test ─────────────────────────────────────────────────── + +echo "" +echo "▸ Docker image availability" + +# Just check if the image reference is parseable (don't actually pull — too slow for a test) +IMAGE=$(docker compose config 2>/dev/null | grep "image:" | head -1 | awk '{print $2}') +if [ -n "$IMAGE" ]; then + pass "OpenClaw image reference: $IMAGE" +else + fail "OpenClaw image reference" "could not extract image name" +fi + +QDRANT_IMAGE=$(docker compose -f docker-compose.yml -f docker-compose.oss.yml config 2>/dev/null | grep "image:" | grep qdrant | awk '{print $2}') +if [ -n "$QDRANT_IMAGE" ]; then + pass "Qdrant image reference: $QDRANT_IMAGE" +else + fail "Qdrant image reference" "could not extract image name" +fi + +# ── setup.sh simulation ───────────────────────────────────────────────────── + +echo "" +echo "▸ setup.sh config generation simulation" + +# Test the sed substitution that setup.sh does +TEST_CONFIG_DIR=$(mktemp -d) +sed "s/__MEM0_USER_ID__/testuser/g" "$SCRIPT_DIR/config/openclaw.platform.json" > "$TEST_CONFIG_DIR/openclaw.json" + +if python3 -c " +import json +c = json.load(open('$TEST_CONFIG_DIR/openclaw.json')) +p = c['plugins']['openclaw-mem0'] +assert p['userId'] == 'testuser', f'expected testuser, got {p[\"userId\"]}' +assert p['apiKey'] == '\${MEM0_API_KEY}', 'apiKey should remain as env var template' +" 2>/dev/null; then + pass "sed substitution works correctly (userId baked, apiKey templated)" +else + fail "sed substitution" "userId not replaced or apiKey was wrongly replaced" +fi + +rm -rf "$TEST_CONFIG_DIR" + +# OSS config substitution +TEST_CONFIG_DIR=$(mktemp -d) +sed "s/__MEM0_USER_ID__/testuser/g" "$SCRIPT_DIR/config/openclaw.oss.json" > "$TEST_CONFIG_DIR/openclaw.json" + +if python3 -c " +import json +c = json.load(open('$TEST_CONFIG_DIR/openclaw.json')) +p = c['plugins']['openclaw-mem0'] +assert p['userId'] == 'testuser' +assert p['oss']['vectorStore']['config']['host'] == 'qdrant' +" 2>/dev/null; then + pass "OSS config sed substitution works correctly" +else + fail "OSS config sed substitution" "fields incorrect after substitution" +fi + +rm -rf "$TEST_CONFIG_DIR" + +# ── Cleanup ────────────────────────────────────────────────────────────────── + +if [ "$TEMP_ENV" = true ]; then + rm -f "$SCRIPT_DIR/.env" +fi + +# ── Summary ────────────────────────────────────────────────────────────────── + +echo "" +echo "═══════════════════════════════════════════════════" +TOTAL=$((PASS + FAIL)) +if [ $FAIL -eq 0 ]; then + echo " All $TOTAL tests passed ✔" +else + echo " $PASS/$TOTAL passed, $FAIL failed" + echo "" + for t in "${TESTS[@]}"; do + if [[ "$t" == "✖"* ]]; then + echo " $t" + fi + done +fi +echo "═══════════════════════════════════════════════════" +echo "" + +exit $FAIL