Compare commits

...

1 Commits

Author SHA1 Message Date
kartik-mem0 e0fb194c76 feat: add one-click Docker setup for OpenClaw + Mem0 2026-04-01 10:55:50 +05:30
9 changed files with 792 additions and 0 deletions
+26
View File
@@ -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
+102
View File
@@ -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)
+21
View File
@@ -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
}
}
}
}
}
}
@@ -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
}
}
}
+23
View File
@@ -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:
+22
View File
@@ -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
+268
View File
@@ -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" <<EOF
# Generated by setup.sh on $(date -u +"%Y-%m-%dT%H:%M:%SZ")
MEM0_API_KEY=${MEM0_API_KEY}
OPENAI_API_KEY=${OPENAI_API_KEY}
MEM0_USER_ID=${MEM0_USER_ID}
OPENCLAW_PORT=${OPENCLAW_PORT}
OPENCLAW_CONFIG_DIR=${OPENCLAW_CONFIG_DIR}
TZ=${TZ:-UTC}
EOF
ok "Created .env"
# ============================================================================
# Step 7: Generate OpenClaw config
# ============================================================================
if [ "$SKIP_CONFIG" = false ]; then
mkdir -p "$OPENCLAW_CONFIG_DIR"
if [ "$MODE" = "platform" ]; then
TEMPLATE="$SCRIPT_DIR/config/openclaw.platform.json"
else
TEMPLATE="$SCRIPT_DIR/config/openclaw.oss.json"
fi
# Substitute the user ID placeholder (apiKey uses env var resolution at runtime)
sed "s/__MEM0_USER_ID__/${MEM0_USER_ID}/g" "$TEMPLATE" > "$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 ""
@@ -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)
+280
View File
@@ -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