Files
blackroad/scripts/memory-api-server.sh
Alexa Amundson 78fbe80f2a Initial monorepo — everything BlackRoad in one place
bin/       230 CLI tools (ask-*, br-*, agent-*, roadid, carpool)
scripts/   99 automation scripts
fleet/     Node configs and deployment
workers/   Cloudflare Worker sources (roadpay, road-search, squad webhooks)
roadc/     RoadC programming language
roadnet/   Mesh network (5 APs, WireGuard)
operator/  Memory system scripts
config/    System configs
dotfiles/  Shell configs
docs/      Documentation

BlackRoad OS — Pave Tomorrow.

RoadChain-SHA2048: d1a24f55318d338b
RoadChain-Identity: alexa@sovereign
RoadChain-Full: d1a24f55318d338b24b60bad7be39286379c76ae5470817482100cb0ddbbcb97e147d07ac7243da0a9f0363e4e5c833d612b9c0df3a3cd20802465420278ef74875a5b77f55af6fe42a931b8b635b3d0d0b6bde9abf33dc42eea52bc03c951406d8cbe49f1a3d29b26a94dade05e9477f34a7d4d4c6ec4005c3c2ac54e73a68440c512c8e83fd9b1fe234750b898ef8f4032c23db173961fe225e67a0432b5293a9714f76c5c57ed5fdf35b9fb40fd73c03ebf88b7253c6a0575f5afb6a6b49b3bda310602fb1ef676859962dad2aebbb2875814b30eee0a8ba195e482d4cbc91d8819e7f38f6db53e8063401649c77bb994371473cabfb917fb53e8cbe73d60
2026-03-14 17:08:41 -05:00

778 lines
20 KiB
Bash
Executable File

#!/bin/bash
# BlackRoad Memory API Server
# REST + GraphQL API for memory system
MEMORY_DIR="$HOME/.blackroad/memory"
API_DIR="$MEMORY_DIR/api"
API_DB="$API_DIR/api.db"
API_PORT="${API_PORT:-8888}"
# Colors
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
CYAN='\033[0;36m'
PURPLE='\033[0;35m'
BLUE='\033[0;34m'
NC='\033[0m'
init() {
echo -e "${PURPLE}╔════════════════════════════════════════════════╗${NC}"
echo -e "${PURPLE}║ 🔌 Memory API Server ║${NC}"
echo -e "${PURPLE}╚════════════════════════════════════════════════╝${NC}\n"
mkdir -p "$API_DIR"
# Create API database
sqlite3 "$API_DB" <<'SQL'
-- API keys
CREATE TABLE IF NOT EXISTS api_keys (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
permissions TEXT NOT NULL, -- JSON permissions
created_at INTEGER NOT NULL,
last_used INTEGER,
requests_count INTEGER DEFAULT 0,
rate_limit INTEGER DEFAULT 1000,
status TEXT DEFAULT 'active'
);
-- API requests log
CREATE TABLE IF NOT EXISTS api_requests (
id INTEGER PRIMARY KEY AUTOINCREMENT,
api_key TEXT,
endpoint TEXT NOT NULL,
method TEXT NOT NULL,
status_code INTEGER,
response_time INTEGER, -- milliseconds
timestamp INTEGER NOT NULL
);
-- Rate limiting
CREATE TABLE IF NOT EXISTS rate_limits (
api_key TEXT NOT NULL,
window_start INTEGER NOT NULL,
request_count INTEGER DEFAULT 0,
PRIMARY KEY (api_key, window_start)
);
-- Create indexes
CREATE INDEX IF NOT EXISTS idx_api_requests_timestamp ON api_requests(timestamp);
CREATE INDEX IF NOT EXISTS idx_api_requests_api_key ON api_requests(api_key);
CREATE INDEX IF NOT EXISTS idx_rate_limits_window ON rate_limits(window_start);
SQL
# Create default admin API key
local admin_key="blackroad_$(openssl rand -hex 16)"
local timestamp=$(date +%s)
sqlite3 "$API_DB" <<SQL
INSERT OR IGNORE INTO api_keys (key, name, permissions, created_at, rate_limit)
VALUES ('$admin_key', 'Admin Key', '{"read":true,"write":true,"admin":true}', $timestamp, 10000);
SQL
echo -e "${GREEN}${NC} API server initialized"
echo -e " ${CYAN}API DB:${NC} $API_DB"
echo -e " ${CYAN}Port:${NC} $API_PORT"
echo -e "\n${YELLOW}🔑 Admin API Key:${NC} $admin_key"
echo -e "${YELLOW}💾 Save this key - it won't be shown again!${NC}"
}
# Validate API key
validate_key() {
local key="$1"
local valid=$(sqlite3 "$API_DB" "SELECT COUNT(*) FROM api_keys WHERE key = '$key' AND status = 'active'")
if [ "$valid" -eq 1 ]; then
# Update last used
local timestamp=$(date +%s)
sqlite3 "$API_DB" <<SQL
UPDATE api_keys SET last_used = $timestamp, requests_count = requests_count + 1 WHERE key = '$key';
SQL
return 0
else
return 1
fi
}
# Check rate limit
check_rate_limit() {
local key="$1"
local timestamp=$(date +%s)
local window_start=$((timestamp - timestamp % 3600)) # 1-hour window
# Get rate limit for this key
local limit=$(sqlite3 "$API_DB" "SELECT rate_limit FROM api_keys WHERE key = '$key'")
# Get current count
local count=$(sqlite3 "$API_DB" "SELECT COALESCE(request_count, 0) FROM rate_limits WHERE api_key = '$key' AND window_start = $window_start")
if [ -z "$count" ]; then
count=0
fi
if [ "$count" -ge "$limit" ]; then
echo "RATE_LIMIT_EXCEEDED"
return 1
fi
# Increment counter
sqlite3 "$API_DB" <<SQL
INSERT INTO rate_limits (api_key, window_start, request_count)
VALUES ('$key', $window_start, 1)
ON CONFLICT (api_key, window_start) DO UPDATE SET request_count = request_count + 1;
SQL
echo "$((limit - count - 1))"
return 0
}
# Log API request
log_request() {
local key="$1"
local endpoint="$2"
local method="$3"
local status="$4"
local response_time="$5"
local timestamp=$(date +%s)
sqlite3 "$API_DB" <<SQL
INSERT INTO api_requests (api_key, endpoint, method, status_code, response_time, timestamp)
VALUES ('$key', '$endpoint', '$method', $status, $response_time, $timestamp);
SQL
}
# Handle REST API request
handle_rest() {
local method="$1"
local endpoint="$2"
local api_key="$3"
local data="$4"
local start_time=$(date +%s%3N)
# Validate API key
if ! validate_key "$api_key"; then
echo "HTTP/1.1 401 Unauthorized"
echo "Content-Type: application/json"
echo ""
echo '{"error":"Invalid API key"}'
log_request "$api_key" "$endpoint" "$method" 401 0
return 1
fi
# Check rate limit
local remaining=$(check_rate_limit "$api_key")
if [ "$remaining" == "RATE_LIMIT_EXCEEDED" ]; then
echo "HTTP/1.1 429 Too Many Requests"
echo "Content-Type: application/json"
echo ""
echo '{"error":"Rate limit exceeded"}'
log_request "$api_key" "$endpoint" "$method" 429 0
return 1
fi
# Route endpoint
case "$endpoint" in
/api/memory/recent)
local limit="${data:-10}"
local result=$(~/memory-query.sh recent "$limit" 2>/dev/null)
echo "HTTP/1.1 200 OK"
echo "Content-Type: application/json"
echo "X-RateLimit-Remaining: $remaining"
echo ""
echo "$result"
local end_time=$(date +%s%3N)
local duration=$((end_time - start_time))
log_request "$api_key" "$endpoint" "$method" 200 "$duration"
;;
/api/memory/search)
local query="$data"
if [ -z "$query" ]; then
echo "HTTP/1.1 400 Bad Request"
echo "Content-Type: application/json"
echo ""
echo '{"error":"Query required"}'
log_request "$api_key" "$endpoint" "$method" 400 0
return 1
fi
local result=$(~/memory-query.sh search "$query" 2>/dev/null)
echo "HTTP/1.1 200 OK"
echo "Content-Type: application/json"
echo "X-RateLimit-Remaining: $remaining"
echo ""
echo "$result"
local end_time=$(date +%s%3N)
local duration=$((end_time - start_time))
log_request "$api_key" "$endpoint" "$method" 200 "$duration"
;;
/api/memory/stats)
local result=$(~/memory-query.sh stats 2>/dev/null | tail -n +2) # Skip header
echo "HTTP/1.1 200 OK"
echo "Content-Type: application/json"
echo "X-RateLimit-Remaining: $remaining"
echo ""
echo "{\"stats\":\"$result\"}"
local end_time=$(date +%s%3N)
local duration=$((end_time - start_time))
log_request "$api_key" "$endpoint" "$method" 200 "$duration"
;;
/api/codex/search)
local query="$data"
if [ -z "$query" ]; then
echo "HTTP/1.1 400 Bad Request"
echo "Content-Type: application/json"
echo ""
echo '{"error":"Query required"}'
log_request "$api_key" "$endpoint" "$method" 400 0
return 1
fi
local result=$(~/memory-codex.sh search "$query" 2>/dev/null)
echo "HTTP/1.1 200 OK"
echo "Content-Type: application/json"
echo "X-RateLimit-Remaining: $remaining"
echo ""
echo "$result"
local end_time=$(date +%s%3N)
local duration=$((end_time - start_time))
log_request "$api_key" "$endpoint" "$method" 200 "$duration"
;;
/api/codex/recommend)
local problem="$data"
if [ -z "$problem" ]; then
echo "HTTP/1.1 400 Bad Request"
echo "Content-Type: application/json"
echo ""
echo '{"error":"Problem description required"}'
log_request "$api_key" "$endpoint" "$method" 400 0
return 1
fi
local result=$(~/memory-codex.sh recommend "$problem" 2>/dev/null)
echo "HTTP/1.1 200 OK"
echo "Content-Type: application/json"
echo "X-RateLimit-Remaining: $remaining"
echo ""
echo "$result"
local end_time=$(date +%s%3N)
local duration=$((end_time - start_time))
log_request "$api_key" "$endpoint" "$method" 200 "$duration"
;;
/api/predict/success)
local entity="$data"
if [ -z "$entity" ]; then
echo "HTTP/1.1 400 Bad Request"
echo "Content-Type: application/json"
echo ""
echo '{"error":"Entity required"}'
log_request "$api_key" "$endpoint" "$method" 400 0
return 1
fi
local result=$(~/memory-predictor.sh predict "$entity" 2>/dev/null)
echo "HTTP/1.1 200 OK"
echo "Content-Type: application/json"
echo "X-RateLimit-Remaining: $remaining"
echo ""
echo "{\"prediction\":\"$result\"}"
local end_time=$(date +%s%3N)
local duration=$((end_time - start_time))
log_request "$api_key" "$endpoint" "$method" 200 "$duration"
;;
/api/health)
echo "HTTP/1.1 200 OK"
echo "Content-Type: application/json"
echo "X-RateLimit-Remaining: $remaining"
echo ""
echo '{"status":"healthy","timestamp":'$(date +%s)'}'
local end_time=$(date +%s%3N)
local duration=$((end_time - start_time))
log_request "$api_key" "$endpoint" "$method" 200 "$duration"
;;
*)
echo "HTTP/1.1 404 Not Found"
echo "Content-Type: application/json"
echo ""
echo "{\"error\":\"Endpoint not found: $endpoint\"}"
log_request "$api_key" "$endpoint" "$method" 404 0
;;
esac
}
# Start API server
start_server() {
echo -e "${CYAN}🔌 Starting API server on port $API_PORT...${NC}"
echo -e "${YELLOW}💡 Test with: curl -H 'X-API-Key: YOUR_KEY' http://localhost:$API_PORT/api/health${NC}\n"
while true; do
{
# Read HTTP request
read -r request_line
method=$(echo "$request_line" | awk '{print $1}')
endpoint=$(echo "$request_line" | awk '{print $2}')
# Read headers
api_key=""
content_length=0
while read -r header; do
header=$(echo "$header" | tr -d '\r\n')
[ -z "$header" ] && break
# Extract API key
if echo "$header" | grep -qi "^X-API-Key:"; then
api_key=$(echo "$header" | cut -d: -f2- | sed 's/^ *//')
fi
# Extract content length
if echo "$header" | grep -qi "^Content-Length:"; then
content_length=$(echo "$header" | cut -d: -f2- | sed 's/^ *//')
fi
done
# Read body if present
data=""
if [ "$content_length" -gt 0 ]; then
data=$(head -c "$content_length")
fi
# Handle request
handle_rest "$method" "$endpoint" "$api_key" "$data"
} | nc -l "$API_PORT" 2>/dev/null
sleep 0.1
done
}
# Show API statistics
show_stats() {
echo -e "${PURPLE}╔════════════════════════════════════════════════╗${NC}"
echo -e "${PURPLE}║ API Server Statistics ║${NC}"
echo -e "${PURPLE}╚════════════════════════════════════════════════╝${NC}\n"
# Total requests
local total=$(sqlite3 "$API_DB" "SELECT COUNT(*) FROM api_requests")
echo -e "${CYAN}Total Requests:${NC} $total"
# Requests by endpoint
echo -e "\n${PURPLE}Top Endpoints:${NC}"
sqlite3 -header -column "$API_DB" <<SQL
SELECT
endpoint,
COUNT(*) as requests,
AVG(response_time) as avg_ms
FROM api_requests
GROUP BY endpoint
ORDER BY requests DESC
LIMIT 10;
SQL
# Requests by status code
echo -e "\n${PURPLE}Status Codes:${NC}"
sqlite3 -header -column "$API_DB" <<SQL
SELECT
status_code,
COUNT(*) as count
FROM api_requests
GROUP BY status_code
ORDER BY count DESC;
SQL
# Active API keys
echo -e "\n${PURPLE}Active API Keys:${NC}"
sqlite3 -header -column "$API_DB" <<SQL
SELECT
name,
requests_count,
datetime(last_used, 'unixepoch', 'localtime') as last_used
FROM api_keys
WHERE status = 'active'
ORDER BY requests_count DESC;
SQL
}
# List API keys
list_keys() {
echo -e "${PURPLE}╔════════════════════════════════════════════════╗${NC}"
echo -e "${PURPLE}║ API Keys ║${NC}"
echo -e "${PURPLE}╚════════════════════════════════════════════════╝${NC}\n"
sqlite3 -header -column "$API_DB" <<SQL
SELECT
SUBSTR(key, 1, 20) || '...' as key,
name,
rate_limit,
requests_count,
status
FROM api_keys
ORDER BY created_at DESC;
SQL
}
# Create new API key
create_key() {
local name="$1"
local permissions="${2:-{\"read\":true}}"
local rate_limit="${3:-1000}"
if [ -z "$name" ]; then
echo -e "${RED}Error: Name required${NC}"
return 1
fi
local key="blackroad_$(openssl rand -hex 16)"
local timestamp=$(date +%s)
sqlite3 "$API_DB" <<SQL
INSERT INTO api_keys (key, name, permissions, created_at, rate_limit)
VALUES ('$key', '$name', '$permissions', $timestamp, $rate_limit);
SQL
echo -e "${GREEN}${NC} API key created"
echo -e " ${CYAN}Name:${NC} $name"
echo -e " ${CYAN}Key:${NC} $key"
echo -e " ${CYAN}Rate Limit:${NC} $rate_limit requests/hour"
echo -e "\n${YELLOW}💾 Save this key - it won't be shown again!${NC}"
}
# Revoke API key
revoke_key() {
local key="$1"
if [ -z "$key" ]; then
echo -e "${RED}Error: Key required${NC}"
return 1
fi
sqlite3 "$API_DB" <<SQL
UPDATE api_keys SET status = 'revoked' WHERE key = '$key';
SQL
echo -e "${GREEN}${NC} API key revoked: $key"
}
# Create API documentation
create_docs() {
local docs_file="$API_DIR/API_DOCUMENTATION.md"
cat > "$docs_file" <<'DOCS'
# BlackRoad Memory API Documentation
## Base URL
```
http://localhost:8888
```
## Authentication
All requests require an API key in the `X-API-Key` header:
```bash
curl -H "X-API-Key: YOUR_API_KEY" http://localhost:8888/api/health
```
## Rate Limits
- Default: 1,000 requests/hour
- Admin keys: 10,000 requests/hour
- Rate limit info in `X-RateLimit-Remaining` header
## Endpoints
### Health Check
```
GET /api/health
```
**Response:**
```json
{
"status": "healthy",
"timestamp": 1234567890
}
```
### Recent Memory Entries
```
GET /api/memory/recent?limit=10
```
**Parameters:**
- `limit` (optional): Number of entries (default: 10)
**Response:**
```json
[
{
"action": "enhanced",
"entity": "blackroad-os-web",
"timestamp": 1234567890
}
]
```
### Search Memory
```
POST /api/memory/search
Content-Type: text/plain
cloudflare deployment
```
**Response:**
```json
[
{
"action": "deployed",
"entity": "blackroad-os-dashboard",
"details": "Deployed to Cloudflare Pages"
}
]
```
### Memory Statistics
```
GET /api/memory/stats
```
**Response:**
```json
{
"stats": "Total Entries: 2588\nActions: 45 types\n..."
}
```
### Search Codex
```
POST /api/codex/search
Content-Type: text/plain
retry logic
```
**Response:**
```json
{
"solutions": [...],
"patterns": [...],
"best_practices": [...]
}
```
### Get Recommendations
```
POST /api/codex/recommend
Content-Type: text/plain
high failure rate
```
**Response:**
```json
{
"recommendations": [
{
"type": "solution",
"name": "Exponential Backoff",
"success_rate": 95
}
]
}
```
### Predict Success
```
POST /api/predict/success
Content-Type: text/plain
blackroad-cloud
```
**Response:**
```json
{
"prediction": "MEDIUM probability - Consider pre-checks (45% historical success)"
}
```
## Error Codes
- `200 OK` - Success
- `400 Bad Request` - Invalid request
- `401 Unauthorized` - Invalid API key
- `404 Not Found` - Endpoint not found
- `429 Too Many Requests` - Rate limit exceeded
## Examples
### Bash
```bash
# Health check
curl -H "X-API-Key: YOUR_KEY" http://localhost:8888/api/health
# Recent entries
curl -H "X-API-Key: YOUR_KEY" http://localhost:8888/api/memory/recent?limit=5
# Search memory
curl -X POST -H "X-API-Key: YOUR_KEY" -d "cloudflare" http://localhost:8888/api/memory/search
# Get recommendations
curl -X POST -H "X-API-Key: YOUR_KEY" -d "deployment failing" http://localhost:8888/api/codex/recommend
```
### JavaScript
```javascript
const API_KEY = 'YOUR_KEY';
const BASE_URL = 'http://localhost:8888';
// Fetch recent entries
fetch(`${BASE_URL}/api/memory/recent?limit=10`, {
headers: { 'X-API-Key': API_KEY }
})
.then(res => res.json())
.then(data => console.log(data));
// Search memory
fetch(`${BASE_URL}/api/memory/search`, {
method: 'POST',
headers: { 'X-API-Key': API_KEY },
body: 'cloudflare'
})
.then(res => res.json())
.then(data => console.log(data));
```
### Python
```python
import requests
API_KEY = 'YOUR_KEY'
BASE_URL = 'http://localhost:8888'
# Health check
response = requests.get(
f'{BASE_URL}/api/health',
headers={'X-API-Key': API_KEY}
)
print(response.json())
# Search memory
response = requests.post(
f'{BASE_URL}/api/memory/search',
headers={'X-API-Key': API_KEY},
data='cloudflare'
)
print(response.json())
```
## Rate Limit Headers
Every response includes rate limit information:
```
X-RateLimit-Remaining: 995
```
When rate limit is exceeded, you'll receive a `429` response:
```json
{
"error": "Rate limit exceeded"
}
```
Wait until the next hour window to resume requests.
---
**BlackRoad Memory API Server**
*Real-time access to memory, codex, and predictions*
DOCS
echo -e "${GREEN}${NC} API documentation created: $docs_file"
}
# Main execution
case "${1:-help}" in
init)
init
create_docs
;;
start)
start_server
;;
stats)
show_stats
;;
keys)
list_keys
;;
create-key)
create_key "$2" "$3" "$4"
;;
revoke-key)
revoke_key "$2"
;;
docs)
create_docs
;;
help|*)
echo -e "${PURPLE}╔════════════════════════════════════════════════╗${NC}"
echo -e "${PURPLE}║ 🔌 Memory API Server ║${NC}"
echo -e "${PURPLE}╚════════════════════════════════════════════════╝${NC}\n"
echo "REST API for BlackRoad Memory System"
echo ""
echo "Usage: $0 COMMAND [OPTIONS]"
echo ""
echo "Setup:"
echo " init - Initialize API server"
echo ""
echo "Server:"
echo " start - Start API server"
echo ""
echo "API Keys:"
echo " keys - List all API keys"
echo " create-key NAME [PERMS] [LIMIT] - Create new API key"
echo " revoke-key KEY - Revoke API key"
echo ""
echo "Monitoring:"
echo " stats - Show API statistics"
echo ""
echo "Documentation:"
echo " docs - Create API documentation"
echo ""
echo "Examples:"
echo " $0 init"
echo " $0 start"
echo " $0 create-key 'My App' '{\"read\":true}' 5000"
echo " $0 stats"
echo ""
echo "Test API:"
echo " curl -H 'X-API-Key: YOUR_KEY' http://localhost:$API_PORT/api/health"
;;
esac