@contextableai/openclaw-memory-graphiti 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +424 -0
- package/authorization.ts +202 -0
- package/config.ts +111 -0
- package/docker/.env.example +66 -0
- package/docker/docker-compose.yml +151 -0
- package/graphiti.ts +382 -0
- package/index.ts +942 -0
- package/openclaw.plugin.json +91 -0
- package/package.json +51 -0
- package/schema.zed +23 -0
- package/scripts/dev-setup.sh +146 -0
- package/scripts/dev-start.sh +236 -0
- package/scripts/dev-status.sh +57 -0
- package/scripts/dev-stop.sh +47 -0
- package/search.ts +201 -0
- package/spicedb.ts +174 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "memory-graphiti",
|
|
3
|
+
"kind": "memory",
|
|
4
|
+
"uiHints": {
|
|
5
|
+
"spicedb.endpoint": {
|
|
6
|
+
"label": "SpiceDB Endpoint",
|
|
7
|
+
"placeholder": "localhost:50051",
|
|
8
|
+
"help": "SpiceDB gRPC endpoint (host:port)"
|
|
9
|
+
},
|
|
10
|
+
"spicedb.token": {
|
|
11
|
+
"label": "SpiceDB Token",
|
|
12
|
+
"sensitive": true,
|
|
13
|
+
"placeholder": "dev_token",
|
|
14
|
+
"help": "SpiceDB pre-shared key (or use ${SPICEDB_TOKEN})"
|
|
15
|
+
},
|
|
16
|
+
"spicedb.insecure": {
|
|
17
|
+
"label": "Insecure Connection",
|
|
18
|
+
"help": "Allow insecure gRPC to localhost (dev mode)"
|
|
19
|
+
},
|
|
20
|
+
"graphiti.endpoint": {
|
|
21
|
+
"label": "Graphiti MCP Endpoint",
|
|
22
|
+
"placeholder": "http://localhost:8000",
|
|
23
|
+
"help": "Graphiti MCP server HTTP base URL"
|
|
24
|
+
},
|
|
25
|
+
"graphiti.defaultGroupId": {
|
|
26
|
+
"label": "Default Group ID",
|
|
27
|
+
"placeholder": "main",
|
|
28
|
+
"help": "Default Graphiti group_id for memory isolation"
|
|
29
|
+
},
|
|
30
|
+
"subjectType": {
|
|
31
|
+
"label": "Subject Type",
|
|
32
|
+
"placeholder": "agent",
|
|
33
|
+
"help": "SpiceDB subject object type (agent or person)",
|
|
34
|
+
"advanced": true
|
|
35
|
+
},
|
|
36
|
+
"subjectId": {
|
|
37
|
+
"label": "Subject ID",
|
|
38
|
+
"placeholder": "${OPENCLAW_AGENT_ID}",
|
|
39
|
+
"help": "SpiceDB subject ID for the current agent/person"
|
|
40
|
+
},
|
|
41
|
+
"autoCapture": {
|
|
42
|
+
"label": "Auto-Capture",
|
|
43
|
+
"help": "Automatically capture important information from conversations"
|
|
44
|
+
},
|
|
45
|
+
"autoRecall": {
|
|
46
|
+
"label": "Auto-Recall",
|
|
47
|
+
"help": "Automatically inject relevant memories into context"
|
|
48
|
+
},
|
|
49
|
+
"customInstructions": {
|
|
50
|
+
"label": "Custom Extraction Instructions",
|
|
51
|
+
"help": "Natural language rules for what Graphiti should extract from conversations",
|
|
52
|
+
"advanced": true
|
|
53
|
+
},
|
|
54
|
+
"maxCaptureMessages": {
|
|
55
|
+
"label": "Max Capture Messages",
|
|
56
|
+
"help": "Maximum number of recent messages to include in auto-capture (default: 10)",
|
|
57
|
+
"advanced": true
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"configSchema": {
|
|
61
|
+
"type": "object",
|
|
62
|
+
"additionalProperties": false,
|
|
63
|
+
"properties": {
|
|
64
|
+
"spicedb": {
|
|
65
|
+
"type": "object",
|
|
66
|
+
"additionalProperties": false,
|
|
67
|
+
"properties": {
|
|
68
|
+
"endpoint": { "type": "string" },
|
|
69
|
+
"token": { "type": "string" },
|
|
70
|
+
"insecure": { "type": "boolean" }
|
|
71
|
+
},
|
|
72
|
+
"required": ["token"]
|
|
73
|
+
},
|
|
74
|
+
"graphiti": {
|
|
75
|
+
"type": "object",
|
|
76
|
+
"additionalProperties": false,
|
|
77
|
+
"properties": {
|
|
78
|
+
"endpoint": { "type": "string" },
|
|
79
|
+
"defaultGroupId": { "type": "string" }
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"subjectType": { "type": "string", "enum": ["agent", "person"] },
|
|
83
|
+
"subjectId": { "type": "string" },
|
|
84
|
+
"autoCapture": { "type": "boolean" },
|
|
85
|
+
"autoRecall": { "type": "boolean" },
|
|
86
|
+
"customInstructions": { "type": "string" },
|
|
87
|
+
"maxCaptureMessages": { "type": "integer", "minimum": 1, "maximum": 50 }
|
|
88
|
+
},
|
|
89
|
+
"required": ["spicedb"]
|
|
90
|
+
}
|
|
91
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@contextableai/openclaw-memory-graphiti",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "OpenClaw two-layer memory plugin: SpiceDB authorization + Graphiti knowledge graph",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/Contextable/openclaw-memory-graphiti.git"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"openclaw",
|
|
13
|
+
"memory",
|
|
14
|
+
"graphiti",
|
|
15
|
+
"spicedb",
|
|
16
|
+
"knowledge-graph",
|
|
17
|
+
"authorization"
|
|
18
|
+
],
|
|
19
|
+
"files": [
|
|
20
|
+
"index.ts",
|
|
21
|
+
"config.ts",
|
|
22
|
+
"authorization.ts",
|
|
23
|
+
"graphiti.ts",
|
|
24
|
+
"search.ts",
|
|
25
|
+
"spicedb.ts",
|
|
26
|
+
"openclaw.plugin.json",
|
|
27
|
+
"schema.zed",
|
|
28
|
+
"docker/",
|
|
29
|
+
"scripts/"
|
|
30
|
+
],
|
|
31
|
+
"scripts": {
|
|
32
|
+
"test": "vitest run",
|
|
33
|
+
"test:watch": "vitest",
|
|
34
|
+
"test:e2e": "OPENCLAW_LIVE_TEST=1 vitest run --config vitest.e2e.config.ts"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@authzed/authzed-node": "^1.2.0",
|
|
38
|
+
"@sinclair/typebox": "0.34.48"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"vitest": "^4.0.18"
|
|
42
|
+
},
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
45
|
+
},
|
|
46
|
+
"openclaw": {
|
|
47
|
+
"extensions": [
|
|
48
|
+
"./index.ts"
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
}
|
package/schema.zed
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
definition person {}
|
|
2
|
+
|
|
3
|
+
definition agent {
|
|
4
|
+
relation owner: person
|
|
5
|
+
permission act_as = owner
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
definition group {
|
|
9
|
+
relation member: person | agent
|
|
10
|
+
permission access = member
|
|
11
|
+
permission contribute = member
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
definition memory_fragment {
|
|
15
|
+
relation source_group: group
|
|
16
|
+
relation involves: person | agent
|
|
17
|
+
relation shared_by: person | agent
|
|
18
|
+
|
|
19
|
+
// Can view if: directly involved, shared it, or have access to the source group
|
|
20
|
+
permission view = involves + shared_by + source_group->access
|
|
21
|
+
// Can delete if: you shared it (owner-level control)
|
|
22
|
+
permission delete = shared_by
|
|
23
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# -------------------------------------------------------------------
|
|
3
|
+
# dev-setup.sh — One-time installation of native dev services
|
|
4
|
+
#
|
|
5
|
+
# Installs:
|
|
6
|
+
# Redis 8.x from packages.redis.io (>= 7.2 required by FalkorDB)
|
|
7
|
+
# FalkorDB module prebuilt .so from GitHub releases
|
|
8
|
+
# SpiceDB prebuilt binary from GitHub releases
|
|
9
|
+
# Graphiti MCP cloned from GitHub, deps via uv
|
|
10
|
+
#
|
|
11
|
+
# Everything goes into .dev/ (project-local, gitignored) except
|
|
12
|
+
# Redis which is installed system-wide via apt.
|
|
13
|
+
#
|
|
14
|
+
# Requirements: curl, git, python3 (>= 3.10), sudo (for apt)
|
|
15
|
+
# -------------------------------------------------------------------
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
19
|
+
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
20
|
+
DEV_DIR="$PROJECT_DIR/.dev"
|
|
21
|
+
|
|
22
|
+
SPICEDB_VERSION="${SPICEDB_VERSION:-1.49.0}"
|
|
23
|
+
FALKORDB_VERSION="${FALKORDB_VERSION:-4.16.3}"
|
|
24
|
+
|
|
25
|
+
# Detect architecture
|
|
26
|
+
ARCH="$(uname -m)"
|
|
27
|
+
case "$ARCH" in
|
|
28
|
+
aarch64|arm64) ARCH_SUFFIX="arm64"; FALKOR_ARCH="arm64v8" ;;
|
|
29
|
+
x86_64|amd64) ARCH_SUFFIX="amd64"; FALKOR_ARCH="x64" ;;
|
|
30
|
+
*) echo "Unsupported architecture: $ARCH"; exit 1 ;;
|
|
31
|
+
esac
|
|
32
|
+
|
|
33
|
+
echo "==> Setting up dev environment in $DEV_DIR"
|
|
34
|
+
mkdir -p "$DEV_DIR/bin" "$DEV_DIR/lib" "$DEV_DIR/data/falkordb" "$DEV_DIR/logs" "$DEV_DIR/pids"
|
|
35
|
+
|
|
36
|
+
# -------------------------------------------------------------------
|
|
37
|
+
# 1. Redis (system-wide via apt — FalkorDB needs >= 7.2)
|
|
38
|
+
# -------------------------------------------------------------------
|
|
39
|
+
REDIS_OK=false
|
|
40
|
+
if command -v redis-server &>/dev/null; then
|
|
41
|
+
REDIS_MAJOR="$(redis-server --version | grep -oP 'v=\K[0-9]+')"
|
|
42
|
+
if [ "$REDIS_MAJOR" -ge 8 ] 2>/dev/null; then
|
|
43
|
+
REDIS_OK=true
|
|
44
|
+
echo "==> Redis already installed: $(redis-server --version | grep -oP 'v=\S+')"
|
|
45
|
+
fi
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
if [ "$REDIS_OK" = false ]; then
|
|
49
|
+
echo "==> Installing Redis >= 7.2 from packages.redis.io..."
|
|
50
|
+
curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg 2>/dev/null
|
|
51
|
+
echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs 2>/dev/null || echo bookworm) main" \
|
|
52
|
+
| sudo tee /etc/apt/sources.list.d/redis.list >/dev/null
|
|
53
|
+
sudo apt-get update -qq 2>&1 | tail -1
|
|
54
|
+
sudo apt-get install -y -qq redis-server libgomp1 2>&1 | tail -3
|
|
55
|
+
echo " Installed: $(redis-server --version | grep -oP 'v=\S+')"
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
# Ensure libgomp1 is present (FalkorDB dependency)
|
|
59
|
+
dpkg -l libgomp1 >/dev/null 2>&1 || sudo apt-get install -y -qq libgomp1
|
|
60
|
+
|
|
61
|
+
# -------------------------------------------------------------------
|
|
62
|
+
# 2. FalkorDB module (.so)
|
|
63
|
+
# -------------------------------------------------------------------
|
|
64
|
+
if [ -f "$DEV_DIR/lib/falkordb.so" ]; then
|
|
65
|
+
echo "==> FalkorDB module already downloaded"
|
|
66
|
+
else
|
|
67
|
+
echo "==> Downloading FalkorDB v${FALKORDB_VERSION} module (${FALKOR_ARCH})..."
|
|
68
|
+
curl -fsSL -o "$DEV_DIR/lib/falkordb.so" \
|
|
69
|
+
"https://github.com/FalkorDB/FalkorDB/releases/download/v${FALKORDB_VERSION}/falkordb-${FALKOR_ARCH}.so"
|
|
70
|
+
chmod +x "$DEV_DIR/lib/falkordb.so"
|
|
71
|
+
echo " Downloaded: $DEV_DIR/lib/falkordb.so"
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
# -------------------------------------------------------------------
|
|
75
|
+
# 3. PostgreSQL (optional — persistent datastore for SpiceDB)
|
|
76
|
+
# -------------------------------------------------------------------
|
|
77
|
+
if command -v pg_isready &>/dev/null; then
|
|
78
|
+
echo "==> PostgreSQL already installed: $(psql --version 2>/dev/null | head -1)"
|
|
79
|
+
else
|
|
80
|
+
echo "==> Installing PostgreSQL..."
|
|
81
|
+
sudo apt-get update -qq 2>&1 | tail -1
|
|
82
|
+
sudo apt-get install -y -qq postgresql postgresql-client 2>&1 | tail -3
|
|
83
|
+
echo " Installed: $(psql --version 2>/dev/null | head -1)"
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
# -------------------------------------------------------------------
|
|
87
|
+
# 4. SpiceDB
|
|
88
|
+
# -------------------------------------------------------------------
|
|
89
|
+
if [ -x "$DEV_DIR/bin/spicedb" ]; then
|
|
90
|
+
echo "==> SpiceDB already installed: $("$DEV_DIR/bin/spicedb" version 2>/dev/null || echo 'unknown')"
|
|
91
|
+
else
|
|
92
|
+
echo "==> Downloading SpiceDB v${SPICEDB_VERSION} (${ARCH_SUFFIX})..."
|
|
93
|
+
SPICEDB_URL="https://github.com/authzed/spicedb/releases/download/v${SPICEDB_VERSION}/spicedb_${SPICEDB_VERSION}_linux_${ARCH_SUFFIX}.tar.gz"
|
|
94
|
+
curl -fsSL "$SPICEDB_URL" | tar xz -C "$DEV_DIR/bin" spicedb
|
|
95
|
+
chmod +x "$DEV_DIR/bin/spicedb"
|
|
96
|
+
echo " Installed: $("$DEV_DIR/bin/spicedb" version 2>/dev/null || echo "v${SPICEDB_VERSION}")"
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
# -------------------------------------------------------------------
|
|
100
|
+
# 5. uv (Python package manager)
|
|
101
|
+
# -------------------------------------------------------------------
|
|
102
|
+
if command -v uv &>/dev/null; then
|
|
103
|
+
echo "==> uv already installed: $(uv --version)"
|
|
104
|
+
elif [ -x "$HOME/.local/bin/uv" ]; then
|
|
105
|
+
echo "==> uv already installed at ~/.local/bin/uv"
|
|
106
|
+
export PATH="$HOME/.local/bin:$PATH"
|
|
107
|
+
else
|
|
108
|
+
echo "==> Installing uv (Python package manager)..."
|
|
109
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
110
|
+
export PATH="$HOME/.local/bin:$PATH"
|
|
111
|
+
echo " Installed: $(uv --version)"
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
# -------------------------------------------------------------------
|
|
115
|
+
# 6. Graphiti MCP Server
|
|
116
|
+
# -------------------------------------------------------------------
|
|
117
|
+
GRAPHITI_DIR="$DEV_DIR/graphiti"
|
|
118
|
+
if [ -d "$GRAPHITI_DIR/mcp_server" ]; then
|
|
119
|
+
echo "==> Graphiti repo already cloned, pulling latest..."
|
|
120
|
+
git -C "$GRAPHITI_DIR" pull --ff-only 2>/dev/null || echo " (pull skipped — detached or dirty)"
|
|
121
|
+
else
|
|
122
|
+
echo "==> Cloning Graphiti repo..."
|
|
123
|
+
git clone --depth 1 https://github.com/getzep/graphiti.git "$GRAPHITI_DIR"
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
echo "==> Installing Graphiti MCP server dependencies..."
|
|
127
|
+
cd "$GRAPHITI_DIR/mcp_server"
|
|
128
|
+
if command -v uv &>/dev/null; then
|
|
129
|
+
uv sync 2>&1 | tail -3
|
|
130
|
+
else
|
|
131
|
+
"$HOME/.local/bin/uv" sync 2>&1 | tail -3
|
|
132
|
+
fi
|
|
133
|
+
cd "$PROJECT_DIR"
|
|
134
|
+
|
|
135
|
+
# -------------------------------------------------------------------
|
|
136
|
+
# Done
|
|
137
|
+
# -------------------------------------------------------------------
|
|
138
|
+
echo ""
|
|
139
|
+
echo "==> Dev environment ready!"
|
|
140
|
+
echo ""
|
|
141
|
+
echo " Redis: $(redis-server --version | grep -oP 'v=\S+')"
|
|
142
|
+
echo " FalkorDB: $DEV_DIR/lib/falkordb.so (v${FALKORDB_VERSION})"
|
|
143
|
+
echo " SpiceDB: $DEV_DIR/bin/spicedb"
|
|
144
|
+
echo " Graphiti: $GRAPHITI_DIR/mcp_server/"
|
|
145
|
+
echo ""
|
|
146
|
+
echo " Next: run ./scripts/dev-start.sh to start services"
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# -------------------------------------------------------------------
|
|
3
|
+
# dev-start.sh — Start FalkorDB + SpiceDB + Graphiti MCP server
|
|
4
|
+
#
|
|
5
|
+
# Services run in the background; logs go to .dev/logs/.
|
|
6
|
+
# PID files are written to .dev/pids/.
|
|
7
|
+
#
|
|
8
|
+
# Environment variables (all optional):
|
|
9
|
+
# OPENAI_API_KEY Required by Graphiti for entity extraction
|
|
10
|
+
# SPICEDB_TOKEN Pre-shared key (default: dev_token)
|
|
11
|
+
# SPICEDB_PORT gRPC port (default: 50051)
|
|
12
|
+
# SPICEDB_DATASTORE "memory" (default) or "postgres"
|
|
13
|
+
# SPICEDB_DB_URI Postgres connection URI (when SPICEDB_DATASTORE=postgres)
|
|
14
|
+
# FALKORDB_PORT Redis port (default: 6379)
|
|
15
|
+
# GRAPHITI_PORT HTTP port (default: 8000)
|
|
16
|
+
# -------------------------------------------------------------------
|
|
17
|
+
set -euo pipefail
|
|
18
|
+
|
|
19
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
20
|
+
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
21
|
+
DEV_DIR="$PROJECT_DIR/.dev"
|
|
22
|
+
|
|
23
|
+
# Load .env if present
|
|
24
|
+
if [ -f "$PROJECT_DIR/.env" ]; then
|
|
25
|
+
set -a
|
|
26
|
+
# shellcheck disable=SC1091
|
|
27
|
+
source "$PROJECT_DIR/.env"
|
|
28
|
+
set +a
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
SPICEDB_TOKEN="${SPICEDB_TOKEN:-dev_token}"
|
|
32
|
+
SPICEDB_PORT="${SPICEDB_PORT:-50051}"
|
|
33
|
+
SPICEDB_DATASTORE="${SPICEDB_DATASTORE:-memory}"
|
|
34
|
+
SPICEDB_DB_URI="${SPICEDB_DB_URI:-postgres://spicedb:spicedb_dev@127.0.0.1:5432/spicedb?sslmode=disable}"
|
|
35
|
+
FALKORDB_PORT="${FALKORDB_PORT:-6379}"
|
|
36
|
+
GRAPHITI_PORT="${GRAPHITI_PORT:-8000}"
|
|
37
|
+
|
|
38
|
+
mkdir -p "$DEV_DIR/logs" "$DEV_DIR/pids" "$DEV_DIR/data/falkordb"
|
|
39
|
+
|
|
40
|
+
# Ensure uv is on PATH
|
|
41
|
+
export PATH="$HOME/.local/bin:$DEV_DIR/bin:$PATH"
|
|
42
|
+
|
|
43
|
+
# -------------------------------------------------------------------
|
|
44
|
+
# Helper: check if a process is already running
|
|
45
|
+
# -------------------------------------------------------------------
|
|
46
|
+
is_running() {
|
|
47
|
+
local pidfile="$1"
|
|
48
|
+
if [ -f "$pidfile" ]; then
|
|
49
|
+
local pid
|
|
50
|
+
pid=$(cat "$pidfile")
|
|
51
|
+
if kill -0 "$pid" 2>/dev/null; then
|
|
52
|
+
return 0
|
|
53
|
+
fi
|
|
54
|
+
rm -f "$pidfile"
|
|
55
|
+
fi
|
|
56
|
+
return 1
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# -------------------------------------------------------------------
|
|
60
|
+
# 1. FalkorDB (Redis + module)
|
|
61
|
+
# -------------------------------------------------------------------
|
|
62
|
+
if is_running "$DEV_DIR/pids/falkordb.pid"; then
|
|
63
|
+
echo "==> FalkorDB already running (pid $(cat "$DEV_DIR/pids/falkordb.pid"))"
|
|
64
|
+
else
|
|
65
|
+
echo "==> Starting FalkorDB on port $FALKORDB_PORT..."
|
|
66
|
+
redis-server \
|
|
67
|
+
--loadmodule "$DEV_DIR/lib/falkordb.so" \
|
|
68
|
+
--port "$FALKORDB_PORT" \
|
|
69
|
+
--dir "$DEV_DIR/data/falkordb" \
|
|
70
|
+
--daemonize no \
|
|
71
|
+
--save "" \
|
|
72
|
+
--appendonly no \
|
|
73
|
+
--loglevel notice \
|
|
74
|
+
> "$DEV_DIR/logs/falkordb.log" 2>&1 &
|
|
75
|
+
echo $! > "$DEV_DIR/pids/falkordb.pid"
|
|
76
|
+
echo " PID: $(cat "$DEV_DIR/pids/falkordb.pid") — log: .dev/logs/falkordb.log"
|
|
77
|
+
|
|
78
|
+
# Wait for FalkorDB to be ready
|
|
79
|
+
echo -n " Waiting for FalkorDB..."
|
|
80
|
+
for i in $(seq 1 30); do
|
|
81
|
+
if redis-cli -p "$FALKORDB_PORT" PING 2>/dev/null | grep -q PONG; then
|
|
82
|
+
echo " ready!"
|
|
83
|
+
break
|
|
84
|
+
fi
|
|
85
|
+
if [ "$i" -eq 30 ]; then
|
|
86
|
+
echo " timeout! Check .dev/logs/falkordb.log"
|
|
87
|
+
exit 1
|
|
88
|
+
fi
|
|
89
|
+
sleep 0.5
|
|
90
|
+
echo -n "."
|
|
91
|
+
done
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
# -------------------------------------------------------------------
|
|
95
|
+
# 2. PostgreSQL (when SPICEDB_DATASTORE=postgres)
|
|
96
|
+
# -------------------------------------------------------------------
|
|
97
|
+
if [ "$SPICEDB_DATASTORE" = "postgres" ]; then
|
|
98
|
+
if pg_isready -q 2>/dev/null; then
|
|
99
|
+
echo "==> PostgreSQL already running"
|
|
100
|
+
else
|
|
101
|
+
echo "==> Starting PostgreSQL..."
|
|
102
|
+
sudo pg_ctlcluster 15 main start 2>&1
|
|
103
|
+
echo -n " Waiting for PostgreSQL..."
|
|
104
|
+
for i in $(seq 1 20); do
|
|
105
|
+
if pg_isready -q 2>/dev/null; then
|
|
106
|
+
echo " ready!"
|
|
107
|
+
break
|
|
108
|
+
fi
|
|
109
|
+
if [ "$i" -eq 20 ]; then
|
|
110
|
+
echo " timeout!"
|
|
111
|
+
exit 1
|
|
112
|
+
fi
|
|
113
|
+
sleep 0.5
|
|
114
|
+
echo -n "."
|
|
115
|
+
done
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
# Ensure database and user exist (idempotent)
|
|
119
|
+
sudo su - postgres -c "psql -tc \"SELECT 1 FROM pg_roles WHERE rolname='spicedb'\" | grep -q 1" 2>/dev/null \
|
|
120
|
+
|| sudo su - postgres -c "psql -c \"CREATE USER spicedb WITH PASSWORD 'spicedb_dev';\"" 2>/dev/null
|
|
121
|
+
sudo su - postgres -c "psql -tc \"SELECT 1 FROM pg_database WHERE datname='spicedb'\" | grep -q 1" 2>/dev/null \
|
|
122
|
+
|| sudo su - postgres -c "psql -c \"CREATE DATABASE spicedb OWNER spicedb;\"" 2>/dev/null
|
|
123
|
+
|
|
124
|
+
# Run SpiceDB migrations
|
|
125
|
+
echo "==> Running SpiceDB migrations..."
|
|
126
|
+
"$DEV_DIR/bin/spicedb" migrate head \
|
|
127
|
+
--datastore-engine=postgres \
|
|
128
|
+
"--datastore-conn-uri=$SPICEDB_DB_URI" \
|
|
129
|
+
> "$DEV_DIR/logs/spicedb-migrate.log" 2>&1
|
|
130
|
+
echo " Migrations complete"
|
|
131
|
+
fi
|
|
132
|
+
|
|
133
|
+
# -------------------------------------------------------------------
|
|
134
|
+
# 3. SpiceDB
|
|
135
|
+
# -------------------------------------------------------------------
|
|
136
|
+
if is_running "$DEV_DIR/pids/spicedb.pid"; then
|
|
137
|
+
echo "==> SpiceDB already running (pid $(cat "$DEV_DIR/pids/spicedb.pid"))"
|
|
138
|
+
else
|
|
139
|
+
SPICEDB_ARGS=(
|
|
140
|
+
serve
|
|
141
|
+
"--grpc-preshared-key=$SPICEDB_TOKEN"
|
|
142
|
+
"--grpc-addr=:$SPICEDB_PORT"
|
|
143
|
+
"--datastore-engine=$SPICEDB_DATASTORE"
|
|
144
|
+
--http-enabled=true
|
|
145
|
+
)
|
|
146
|
+
if [ "$SPICEDB_DATASTORE" = "postgres" ]; then
|
|
147
|
+
SPICEDB_ARGS+=("--datastore-conn-uri=$SPICEDB_DB_URI")
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
echo "==> Starting SpiceDB on port $SPICEDB_PORT (datastore: $SPICEDB_DATASTORE)..."
|
|
151
|
+
"$DEV_DIR/bin/spicedb" "${SPICEDB_ARGS[@]}" \
|
|
152
|
+
> "$DEV_DIR/logs/spicedb.log" 2>&1 &
|
|
153
|
+
echo $! > "$DEV_DIR/pids/spicedb.pid"
|
|
154
|
+
echo " PID: $(cat "$DEV_DIR/pids/spicedb.pid") — log: .dev/logs/spicedb.log"
|
|
155
|
+
|
|
156
|
+
# Wait for SpiceDB to be ready
|
|
157
|
+
echo -n " Waiting for SpiceDB..."
|
|
158
|
+
for i in $(seq 1 30); do
|
|
159
|
+
if curl -sf http://localhost:8443/healthz >/dev/null 2>&1; then
|
|
160
|
+
echo " ready!"
|
|
161
|
+
break
|
|
162
|
+
fi
|
|
163
|
+
if [ "$i" -eq 30 ]; then
|
|
164
|
+
echo " timeout! Check .dev/logs/spicedb.log"
|
|
165
|
+
exit 1
|
|
166
|
+
fi
|
|
167
|
+
sleep 0.5
|
|
168
|
+
echo -n "."
|
|
169
|
+
done
|
|
170
|
+
fi
|
|
171
|
+
|
|
172
|
+
# -------------------------------------------------------------------
|
|
173
|
+
# 4. Graphiti MCP Server (FalkorDB backend)
|
|
174
|
+
# -------------------------------------------------------------------
|
|
175
|
+
if is_running "$DEV_DIR/pids/graphiti.pid"; then
|
|
176
|
+
echo "==> Graphiti MCP server already running (pid $(cat "$DEV_DIR/pids/graphiti.pid"))"
|
|
177
|
+
else
|
|
178
|
+
if [ -z "${OPENAI_API_KEY:-}" ]; then
|
|
179
|
+
echo ""
|
|
180
|
+
echo "WARNING: OPENAI_API_KEY is not set."
|
|
181
|
+
echo "Graphiti needs it for entity extraction and embeddings."
|
|
182
|
+
echo "Set it in .env or export it before running this script."
|
|
183
|
+
echo ""
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
echo "==> Starting Graphiti MCP server on port $GRAPHITI_PORT..."
|
|
187
|
+
|
|
188
|
+
GRAPHITI_DIR="$DEV_DIR/graphiti/mcp_server"
|
|
189
|
+
|
|
190
|
+
# Set environment for Graphiti
|
|
191
|
+
export OPENAI_API_KEY="${OPENAI_API_KEY:-}"
|
|
192
|
+
export FALKORDB_URI="redis://localhost:$FALKORDB_PORT"
|
|
193
|
+
|
|
194
|
+
cd "$GRAPHITI_DIR"
|
|
195
|
+
uv run main.py \
|
|
196
|
+
--transport http \
|
|
197
|
+
--port "$GRAPHITI_PORT" \
|
|
198
|
+
> "$DEV_DIR/logs/graphiti.log" 2>&1 &
|
|
199
|
+
echo $! > "$DEV_DIR/pids/graphiti.pid"
|
|
200
|
+
cd "$PROJECT_DIR"
|
|
201
|
+
|
|
202
|
+
echo " PID: $(cat "$DEV_DIR/pids/graphiti.pid") — log: .dev/logs/graphiti.log"
|
|
203
|
+
|
|
204
|
+
# Wait for Graphiti to be ready
|
|
205
|
+
echo -n " Waiting for Graphiti..."
|
|
206
|
+
for i in $(seq 1 60); do
|
|
207
|
+
if curl -sf "http://localhost:$GRAPHITI_PORT/health" >/dev/null 2>&1; then
|
|
208
|
+
echo " ready!"
|
|
209
|
+
break
|
|
210
|
+
fi
|
|
211
|
+
if [ "$i" -eq 60 ]; then
|
|
212
|
+
echo " timeout! Check .dev/logs/graphiti.log"
|
|
213
|
+
exit 1
|
|
214
|
+
fi
|
|
215
|
+
sleep 1
|
|
216
|
+
echo -n "."
|
|
217
|
+
done
|
|
218
|
+
fi
|
|
219
|
+
|
|
220
|
+
# -------------------------------------------------------------------
|
|
221
|
+
# Summary
|
|
222
|
+
# -------------------------------------------------------------------
|
|
223
|
+
echo ""
|
|
224
|
+
echo "==> All services running:"
|
|
225
|
+
echo " FalkorDB: localhost:$FALKORDB_PORT (Redis protocol)"
|
|
226
|
+
echo " SpiceDB gRPC: localhost:$SPICEDB_PORT (datastore: $SPICEDB_DATASTORE)"
|
|
227
|
+
echo " SpiceDB HTTP: localhost:8443"
|
|
228
|
+
echo " Graphiti MCP: http://localhost:$GRAPHITI_PORT"
|
|
229
|
+
echo " Graphiti health: http://localhost:$GRAPHITI_PORT/health"
|
|
230
|
+
echo ""
|
|
231
|
+
echo " Stop with: ./scripts/dev-stop.sh"
|
|
232
|
+
echo " Status: ./scripts/dev-status.sh"
|
|
233
|
+
echo " Logs: tail -f .dev/logs/*.log"
|
|
234
|
+
echo ""
|
|
235
|
+
echo " Run e2e tests:"
|
|
236
|
+
echo " OPENCLAW_LIVE_TEST=1 pnpm vitest run e2e.test.ts"
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# -------------------------------------------------------------------
|
|
3
|
+
# dev-status.sh — Check status of dev services
|
|
4
|
+
# -------------------------------------------------------------------
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
8
|
+
DEV_DIR="$(cd "$SCRIPT_DIR/.." && pwd)/.dev"
|
|
9
|
+
|
|
10
|
+
RED='\033[0;31m'
|
|
11
|
+
GREEN='\033[0;32m'
|
|
12
|
+
NC='\033[0m' # No Color
|
|
13
|
+
|
|
14
|
+
check_service() {
|
|
15
|
+
local name="$1"
|
|
16
|
+
local pidfile="$DEV_DIR/pids/${name}.pid"
|
|
17
|
+
local health_url="$2"
|
|
18
|
+
|
|
19
|
+
printf " %-20s" "$name:"
|
|
20
|
+
|
|
21
|
+
# Check PID
|
|
22
|
+
if [ -f "$pidfile" ]; then
|
|
23
|
+
local pid
|
|
24
|
+
pid=$(cat "$pidfile")
|
|
25
|
+
if kill -0 "$pid" 2>/dev/null; then
|
|
26
|
+
printf "${GREEN}running${NC} (pid %s)" "$pid"
|
|
27
|
+
else
|
|
28
|
+
printf "${RED}dead${NC} (stale pid %s)" "$pid"
|
|
29
|
+
rm -f "$pidfile"
|
|
30
|
+
echo ""
|
|
31
|
+
return
|
|
32
|
+
fi
|
|
33
|
+
else
|
|
34
|
+
printf "${RED}stopped${NC}"
|
|
35
|
+
echo ""
|
|
36
|
+
return
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# Check health endpoint
|
|
40
|
+
if [ -n "$health_url" ]; then
|
|
41
|
+
if curl -sf "$health_url" >/dev/null 2>&1; then
|
|
42
|
+
printf " ${GREEN}healthy${NC}"
|
|
43
|
+
else
|
|
44
|
+
printf " ${RED}unhealthy${NC}"
|
|
45
|
+
fi
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
echo ""
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
echo ""
|
|
52
|
+
echo "Dev service status:"
|
|
53
|
+
echo ""
|
|
54
|
+
check_service "falkordb" ""
|
|
55
|
+
check_service "spicedb" "http://localhost:8443/healthz"
|
|
56
|
+
check_service "graphiti" "http://localhost:8000/health"
|
|
57
|
+
echo ""
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# -------------------------------------------------------------------
|
|
3
|
+
# dev-stop.sh — Stop all dev services
|
|
4
|
+
# -------------------------------------------------------------------
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
8
|
+
DEV_DIR="$(cd "$SCRIPT_DIR/.." && pwd)/.dev"
|
|
9
|
+
|
|
10
|
+
stop_service() {
|
|
11
|
+
local name="$1"
|
|
12
|
+
local pidfile="$DEV_DIR/pids/${name}.pid"
|
|
13
|
+
|
|
14
|
+
if [ -f "$pidfile" ]; then
|
|
15
|
+
local pid
|
|
16
|
+
pid=$(cat "$pidfile")
|
|
17
|
+
if kill -0 "$pid" 2>/dev/null; then
|
|
18
|
+
echo "==> Stopping $name (pid $pid)..."
|
|
19
|
+
kill "$pid"
|
|
20
|
+
# Wait up to 5 seconds for graceful shutdown
|
|
21
|
+
for _ in $(seq 1 10); do
|
|
22
|
+
if ! kill -0 "$pid" 2>/dev/null; then
|
|
23
|
+
break
|
|
24
|
+
fi
|
|
25
|
+
sleep 0.5
|
|
26
|
+
done
|
|
27
|
+
# Force kill if still running
|
|
28
|
+
if kill -0 "$pid" 2>/dev/null; then
|
|
29
|
+
echo " Force-killing $name..."
|
|
30
|
+
kill -9 "$pid" 2>/dev/null || true
|
|
31
|
+
fi
|
|
32
|
+
echo " $name stopped."
|
|
33
|
+
else
|
|
34
|
+
echo "==> $name not running (stale pid file)."
|
|
35
|
+
fi
|
|
36
|
+
rm -f "$pidfile"
|
|
37
|
+
else
|
|
38
|
+
echo "==> $name not running (no pid file)."
|
|
39
|
+
fi
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
stop_service "graphiti"
|
|
43
|
+
stop_service "spicedb"
|
|
44
|
+
stop_service "falkordb"
|
|
45
|
+
|
|
46
|
+
echo ""
|
|
47
|
+
echo "==> All services stopped."
|