Clean and organize project structure
Major reorganization: - Created scripts/ directory for all utility scripts - Created config/ directory for configuration files - Moved all test files to tests/ directory - Updated all script paths to work with new structure - Updated README.md with new project structure diagram New structure: ├── src/ # Source code (API + MCP) ├── scripts/ # Utility scripts (start-*.sh, docs_server.py, etc.) ├── tests/ # All test files and debug utilities ├── config/ # Configuration files (JSON, Caddy config) ├── docs/ # Documentation website └── logs/ # Log files All scripts updated to use relative paths from project root. Documentation updated with new folder structure. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
105
scripts/docs_server.py
Executable file
105
scripts/docs_server.py
Executable file
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simple authenticated web server for LangMem documentation
|
||||
"""
|
||||
|
||||
import http.server
|
||||
import socketserver
|
||||
import base64
|
||||
import os
|
||||
from urllib.parse import parse_qs
|
||||
|
||||
# Authentication credentials
|
||||
USERNAME = "langmem"
|
||||
PASSWORD = "langmem2025"
|
||||
|
||||
class AuthHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
|
||||
"""HTTP handler with basic authentication"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, directory="docs", **kwargs)
|
||||
|
||||
def do_GET(self):
|
||||
"""Handle GET requests with authentication"""
|
||||
if self.authenticate():
|
||||
super().do_GET()
|
||||
else:
|
||||
self.send_auth_request()
|
||||
|
||||
def authenticate(self):
|
||||
"""Check if request has valid authentication"""
|
||||
auth_header = self.headers.get('Authorization')
|
||||
|
||||
if not auth_header:
|
||||
return False
|
||||
|
||||
try:
|
||||
auth_type, credentials = auth_header.split(' ', 1)
|
||||
if auth_type.lower() != 'basic':
|
||||
return False
|
||||
|
||||
decoded = base64.b64decode(credentials).decode('utf-8')
|
||||
username, password = decoded.split(':', 1)
|
||||
|
||||
return username == USERNAME and password == PASSWORD
|
||||
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def send_auth_request(self):
|
||||
"""Send authentication request to client"""
|
||||
self.send_response(401)
|
||||
self.send_header('WWW-Authenticate', 'Basic realm="LangMem Documentation"')
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.end_headers()
|
||||
|
||||
html = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Authentication Required</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; text-align: center; margin-top: 100px; }
|
||||
.container { max-width: 400px; margin: 0 auto; padding: 20px; border: 1px solid #ccc; border-radius: 10px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h2>🔐 LangMem Documentation</h2>
|
||||
<p>Authentication required to access documentation.</p>
|
||||
<p><strong>Username:</strong> langmem<br>
|
||||
<strong>Password:</strong> langmem2025</p>
|
||||
<p>Please refresh the page and enter your credentials when prompted.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
self.wfile.write(html.encode())
|
||||
|
||||
def start_server(port=8080):
|
||||
"""Start the authenticated documentation server"""
|
||||
try:
|
||||
with socketserver.TCPServer(("", port), AuthHTTPRequestHandler) as httpd:
|
||||
print(f"🌐 LangMem Documentation Server started!")
|
||||
print(f"📍 URL: http://localhost:{port}")
|
||||
print(f"🔑 Username: {USERNAME}")
|
||||
print(f"🔑 Password: {PASSWORD}")
|
||||
print(f"🛑 Press Ctrl+C to stop the server")
|
||||
print("=" * 50)
|
||||
httpd.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
print("\n🛑 Server stopped by user")
|
||||
except Exception as e:
|
||||
print(f"❌ Error starting server: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
port = 8080
|
||||
if len(sys.argv) > 1:
|
||||
try:
|
||||
port = int(sys.argv[1])
|
||||
except ValueError:
|
||||
print("Invalid port number. Using default port 8080.")
|
||||
|
||||
start_server(port)
|
||||
156
scripts/get-claude-token.py
Executable file
156
scripts/get-claude-token.py
Executable file
@@ -0,0 +1,156 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Get access token for Claude Matrix user and join Home Assistant room
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import httpx
|
||||
import json
|
||||
|
||||
MATRIX_HOMESERVER = "https://matrix.klas.chat"
|
||||
CLAUDE_USERNAME = "claude"
|
||||
CLAUDE_PASSWORD = "claude_assistant_2025"
|
||||
HOME_ASSISTANT_ROOM_ID = "!xZkScMybPseErYMJDz:matrix.klas.chat"
|
||||
|
||||
async def login_and_get_token():
|
||||
"""Login as Claude user and get access token"""
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
# Login request
|
||||
response = await client.post(
|
||||
f"{MATRIX_HOMESERVER}/_matrix/client/v3/login",
|
||||
json={
|
||||
"type": "m.login.password",
|
||||
"user": CLAUDE_USERNAME,
|
||||
"password": CLAUDE_PASSWORD
|
||||
}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
access_token = data["access_token"]
|
||||
user_id = data["user_id"]
|
||||
|
||||
print(f"✅ Successfully logged in as {user_id}")
|
||||
print(f"🔑 Access Token: {access_token}")
|
||||
|
||||
return access_token, user_id
|
||||
else:
|
||||
print(f"❌ Login failed: {response.status_code}")
|
||||
print(f"Response: {response.text}")
|
||||
return None, None
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error during login: {e}")
|
||||
return None, None
|
||||
|
||||
async def join_home_assistant_room(access_token):
|
||||
"""Join the Home Assistant room"""
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
headers = {"Authorization": f"Bearer {access_token}"}
|
||||
|
||||
# Join room
|
||||
response = await client.post(
|
||||
f"{MATRIX_HOMESERVER}/_matrix/client/v3/join/{HOME_ASSISTANT_ROOM_ID}",
|
||||
headers=headers
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
print(f"✅ Successfully joined Home Assistant room")
|
||||
return True
|
||||
else:
|
||||
print(f"❌ Failed to join room: {response.status_code}")
|
||||
print(f"Response: {response.text}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error joining room: {e}")
|
||||
return False
|
||||
|
||||
async def send_test_message(access_token):
|
||||
"""Send a test message to verify everything works"""
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
headers = {
|
||||
"Authorization": f"Bearer {access_token}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
message = "🤖 Hello! Claude Assistant is now connected to Matrix and ready to send you updates about your projects."
|
||||
|
||||
response = await client.post(
|
||||
f"{MATRIX_HOMESERVER}/_matrix/client/v3/rooms/{HOME_ASSISTANT_ROOM_ID}/send/m.room.message",
|
||||
headers=headers,
|
||||
json={
|
||||
"msgtype": "m.text",
|
||||
"body": message
|
||||
}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
print(f"✅ Test message sent successfully!")
|
||||
return True
|
||||
else:
|
||||
print(f"❌ Failed to send test message: {response.status_code}")
|
||||
print(f"Response: {response.text}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error sending test message: {e}")
|
||||
return False
|
||||
|
||||
async def main():
|
||||
"""Main function"""
|
||||
print("🤖 Setting up Claude Matrix Integration")
|
||||
print("=" * 50)
|
||||
|
||||
# Step 1: Login and get token
|
||||
print("\n1. Logging in as Claude user...")
|
||||
access_token, user_id = await login_and_get_token()
|
||||
|
||||
if not access_token:
|
||||
print("\n❌ Cannot proceed without access token.")
|
||||
print("Make sure the Claude user has been created first!")
|
||||
return
|
||||
|
||||
# Step 2: Join Home Assistant room
|
||||
print("\n2. Joining Home Assistant room...")
|
||||
joined = await join_home_assistant_room(access_token)
|
||||
|
||||
if not joined:
|
||||
print("\n❌ Cannot proceed without joining the room.")
|
||||
return
|
||||
|
||||
# Step 3: Send test message
|
||||
print("\n3. Sending test message...")
|
||||
message_sent = await send_test_message(access_token)
|
||||
|
||||
# Step 4: Output configuration for Claude
|
||||
print("\n" + "=" * 50)
|
||||
print("🎉 Claude Matrix Setup Complete!")
|
||||
print("\n📋 Configuration for Claude's global config:")
|
||||
print(f" Matrix Homeserver: {MATRIX_HOMESERVER}")
|
||||
print(f" Claude User ID: {user_id}")
|
||||
print(f" Claude Access Token: {access_token}")
|
||||
print(f" Home Assistant Room: {HOME_ASSISTANT_ROOM_ID}")
|
||||
|
||||
# Save to file for easy copying
|
||||
config = {
|
||||
"matrix_homeserver": MATRIX_HOMESERVER,
|
||||
"claude_user_id": user_id,
|
||||
"claude_access_token": access_token,
|
||||
"home_assistant_room_id": HOME_ASSISTANT_ROOM_ID,
|
||||
"credentials": {
|
||||
"username": CLAUDE_USERNAME,
|
||||
"password": CLAUDE_PASSWORD
|
||||
}
|
||||
}
|
||||
|
||||
with open("/home/klas/langmem/claude-matrix-config.json", "w") as f:
|
||||
json.dump(config, f, indent=2)
|
||||
|
||||
print(f"\n💾 Configuration saved to: /home/klas/langmem/claude-matrix-config.json")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
91
scripts/start-dev.sh
Executable file
91
scripts/start-dev.sh
Executable file
@@ -0,0 +1,91 @@
|
||||
#!/bin/bash
|
||||
|
||||
# LangMem Development Startup Script
|
||||
|
||||
set -e
|
||||
|
||||
echo "🚀 Starting LangMem Development Environment"
|
||||
|
||||
# Check if .env exists, if not copy from example
|
||||
if [ ! -f .env ]; then
|
||||
echo "📋 Creating .env file from example..."
|
||||
cp .env.example .env
|
||||
echo "⚠️ Please update .env with your actual database password"
|
||||
fi
|
||||
|
||||
# Check if required services are running
|
||||
echo "🔍 Checking required services..."
|
||||
|
||||
# Check Ollama
|
||||
if ! curl -s http://localhost:11434/api/tags > /dev/null; then
|
||||
echo "❌ Ollama is not running. Please start Ollama first."
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Ollama is running"
|
||||
|
||||
# Check Supabase
|
||||
if ! docker ps | grep -q supabase-db; then
|
||||
echo "❌ Supabase is not running. Please start Supabase first."
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Supabase is running"
|
||||
|
||||
# Check if localai network exists
|
||||
if ! docker network ls | grep -q localai; then
|
||||
echo "📡 Creating localai network..."
|
||||
docker network create localai
|
||||
fi
|
||||
echo "✅ Docker network 'localai' is ready"
|
||||
|
||||
# Build and start services
|
||||
echo "🏗️ Building and starting LangMem services..."
|
||||
docker compose up -d --build
|
||||
|
||||
# Wait for services to be ready
|
||||
echo "⏳ Waiting for services to be ready..."
|
||||
sleep 10
|
||||
|
||||
# Check service health
|
||||
echo "🔍 Checking service health..."
|
||||
max_retries=30
|
||||
retry_count=0
|
||||
|
||||
while [ $retry_count -lt $max_retries ]; do
|
||||
if curl -s http://localhost:8765/health > /dev/null; then
|
||||
echo "✅ LangMem API is healthy"
|
||||
break
|
||||
fi
|
||||
|
||||
retry_count=$((retry_count + 1))
|
||||
echo "⏳ Waiting for API to be ready (attempt $retry_count/$max_retries)"
|
||||
sleep 2
|
||||
done
|
||||
|
||||
if [ $retry_count -eq $max_retries ]; then
|
||||
echo "❌ API failed to become ready"
|
||||
docker compose logs langmem-api
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Display service status
|
||||
echo ""
|
||||
echo "🎉 LangMem Development Environment is ready!"
|
||||
echo ""
|
||||
echo "📊 Service Status:"
|
||||
echo " - LangMem API: http://localhost:8765"
|
||||
echo " - API Documentation: http://localhost:8765/docs"
|
||||
echo " - Neo4j Browser: http://localhost:7474"
|
||||
echo " - Health Check: http://localhost:8765/health"
|
||||
echo ""
|
||||
echo "🔧 Useful Commands:"
|
||||
echo " - View logs: docker compose logs -f langmem-api"
|
||||
echo " - Run tests: ./scripts/test.sh"
|
||||
echo " - Stop services: docker compose down"
|
||||
echo " - Restart API: docker compose restart langmem-api"
|
||||
echo ""
|
||||
echo "🔑 API Key: langmem_api_key_2025"
|
||||
echo ""
|
||||
|
||||
# Show current health status
|
||||
echo "🏥 Current Health Status:"
|
||||
curl -s http://localhost:8765/health | python3 -m json.tool || echo "Failed to get health status"
|
||||
15
scripts/start-docs-server.sh
Executable file
15
scripts/start-docs-server.sh
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
# Start LangMem Documentation Server with Authentication
|
||||
|
||||
echo "🚀 Starting LangMem Documentation Server..."
|
||||
echo "🔐 Authentication enabled with basic auth"
|
||||
echo ""
|
||||
|
||||
# Change to project root
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
# Default port
|
||||
PORT=${1:-8080}
|
||||
|
||||
# Start the authenticated server
|
||||
python3 scripts/docs_server.py $PORT
|
||||
62
scripts/start-mcp-server.sh
Executable file
62
scripts/start-mcp-server.sh
Executable file
@@ -0,0 +1,62 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Start LangMem MCP Server
|
||||
# This script starts the MCP server for Claude Code integration
|
||||
|
||||
echo "🚀 Starting LangMem MCP Server..."
|
||||
|
||||
# Change to project root
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
# Check if virtual environment exists
|
||||
if [ ! -d "venv" ]; then
|
||||
echo "📦 Creating virtual environment..."
|
||||
python3 -m venv venv
|
||||
fi
|
||||
|
||||
# Activate virtual environment
|
||||
echo "🔄 Activating virtual environment..."
|
||||
source venv/bin/activate
|
||||
|
||||
# Install MCP requirements
|
||||
echo "📥 Installing MCP requirements..."
|
||||
pip install -r src/mcp/requirements.txt
|
||||
|
||||
# Check if LangMem API is running
|
||||
echo "🔍 Checking LangMem API health..."
|
||||
if curl -s -f http://localhost:8765/health > /dev/null; then
|
||||
echo "✅ LangMem API is healthy"
|
||||
else
|
||||
echo "❌ LangMem API is not running!"
|
||||
echo "💡 Start the LangMem API first: ./start-dev.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Set environment variables
|
||||
export LANGMEM_API_URL="http://localhost:8765"
|
||||
export LANGMEM_API_KEY="langmem_api_key_2025"
|
||||
|
||||
# Start MCP server
|
||||
echo "🏃 Starting MCP server..."
|
||||
echo "🔗 Connect from Claude Code using:"
|
||||
echo " Server command: python /home/klas/langmem/src/mcp/server.py"
|
||||
echo " Working directory: /home/klas/langmem"
|
||||
echo ""
|
||||
echo "📖 Available tools:"
|
||||
echo " - store_memory: Store memories with AI relationship extraction"
|
||||
echo " - search_memories: Search memories with hybrid vector + graph search"
|
||||
echo " - retrieve_memories: Retrieve relevant memories for conversation context"
|
||||
echo " - get_user_memories: Get all memories for a specific user"
|
||||
echo " - delete_memory: Delete a specific memory"
|
||||
echo " - health_check: Check LangMem system health"
|
||||
echo ""
|
||||
echo "🌐 Available resources:"
|
||||
echo " - langmem://memories: Memory storage resource"
|
||||
echo " - langmem://search: Search capabilities resource"
|
||||
echo " - langmem://relationships: AI relationships resource"
|
||||
echo " - langmem://health: System health resource"
|
||||
echo ""
|
||||
echo "Press Ctrl+C to stop the server..."
|
||||
echo ""
|
||||
|
||||
python src/mcp/server.py
|
||||
59
scripts/test.sh
Executable file
59
scripts/test.sh
Executable file
@@ -0,0 +1,59 @@
|
||||
#!/bin/bash
|
||||
|
||||
# LangMem Test Runner Script
|
||||
|
||||
set -e
|
||||
|
||||
echo "🧪 Running LangMem API Tests"
|
||||
|
||||
# Change to project root
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
# Check if services are running
|
||||
if ! curl -s http://localhost:8765/health > /dev/null; then
|
||||
echo "❌ LangMem API is not running. Please start with ./scripts/start-dev.sh first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Install test dependencies
|
||||
echo "📦 Installing test dependencies..."
|
||||
pip install -r tests/requirements.txt
|
||||
|
||||
# Run different test suites based on argument
|
||||
case "${1:-all}" in
|
||||
"unit")
|
||||
echo "🔬 Running unit tests..."
|
||||
python -m pytest tests/test_api.py -v -m "not integration"
|
||||
;;
|
||||
"integration")
|
||||
echo "🔗 Running integration tests..."
|
||||
python -m pytest tests/test_integration.py -v -m "integration"
|
||||
;;
|
||||
"all")
|
||||
echo "🎯 Running all tests..."
|
||||
python -m pytest tests/ -v
|
||||
;;
|
||||
"quick")
|
||||
echo "⚡ Running quick tests..."
|
||||
python -m pytest tests/ -v -m "not slow"
|
||||
;;
|
||||
"coverage")
|
||||
echo "📊 Running tests with coverage..."
|
||||
python -m pytest tests/ -v --cov=src --cov-report=html --cov-report=term
|
||||
;;
|
||||
*)
|
||||
echo "❌ Unknown test type: $1"
|
||||
echo "Usage: ./scripts/test.sh [unit|integration|all|quick|coverage]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
echo "✅ Tests completed!"
|
||||
|
||||
# Show service logs if tests failed
|
||||
if [ $? -ne 0 ]; then
|
||||
echo ""
|
||||
echo "❌ Tests failed. Showing recent API logs:"
|
||||
docker-compose logs --tail=50 langmem-api
|
||||
fi
|
||||
Reference in New Issue
Block a user