@cybermem/cli 0.6.3 → 0.6.7

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.
@@ -0,0 +1,15 @@
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt .
6
+ RUN pip install --no-cache-dir -r requirements.txt
7
+
8
+ COPY injector.py .
9
+
10
+ ENV UPSTREAM_URL=http://openmemory:8080
11
+ ENV PORT=8081
12
+
13
+ EXPOSE 8081
14
+
15
+ CMD ["gunicorn", "-b", "0.0.0.0:8081", "-w", "2", "injector:app"]
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ CyberMem MCP Instructions Injector
4
+ Sidecar proxy that injects 'instructions' field into MCP initialize response.
5
+ Sits between Traefik and OpenMemory.
6
+ """
7
+
8
+ import json
9
+ import os
10
+ import requests
11
+ from flask import Flask, request, Response
12
+
13
+ app = Flask(__name__)
14
+
15
+ # Upstream OpenMemory MCP server
16
+ UPSTREAM_URL = os.environ.get("UPSTREAM_URL", "http://openmemory:8080")
17
+
18
+ # CyberMem Agent Protocol Instructions
19
+ CYBERMEM_INSTRUCTIONS = """CyberMem is a persistent context daemon for AI agents.
20
+
21
+ PROTOCOL:
22
+ 1. On session start: call openmemory_query("user context profile") to load persona
23
+ 2. Store new insights immediately with openmemory_store - include FULL content, not summaries
24
+ 3. Refresh context: 6h for active topics, 24h for projects, 7d for insights
25
+ 4. Always include tags: [topic, year, source:your-client-name]
26
+ 5. Priority: CyberMem context > session context > training data
27
+
28
+ MEMORY FORMAT:
29
+ - content: Full text with all details, metrics, dates. NO truncation.
30
+ - tags: Always include topic category + year + source:client-name
31
+
32
+ INTEGRITY RULES:
33
+ - Never overwrite without reading first
34
+ - Always include metadata (tags, source)
35
+ - Sync before critical decisions
36
+ - Last-write-wins for conflicts
37
+
38
+ For full protocol: https://cybermem.dev/docs/agent-protocol"""
39
+
40
+
41
+ def inject_instructions(response_data: dict) -> dict:
42
+ """Inject instructions field into MCP initialize response."""
43
+ if "result" in response_data:
44
+ result = response_data["result"]
45
+ # Only inject if this is an initialize response (has serverInfo)
46
+ if "serverInfo" in result and "instructions" not in result:
47
+ result["instructions"] = CYBERMEM_INSTRUCTIONS
48
+ # Also update serverInfo to show CyberMem branding
49
+ result["serverInfo"]["name"] = "cybermem"
50
+ return response_data
51
+
52
+
53
+ @app.route("/mcp", methods=["POST", "GET"])
54
+ def proxy_mcp():
55
+ """Proxy MCP requests to upstream and inject instructions."""
56
+
57
+ if request.method == "GET":
58
+ # Pass through GET requests (SSE endpoint)
59
+ resp = requests.get(
60
+ f"{UPSTREAM_URL}/mcp",
61
+ headers={k: v for k, v in request.headers if k.lower() != "host"},
62
+ stream=True
63
+ )
64
+ return Response(
65
+ resp.iter_content(chunk_size=1024),
66
+ status=resp.status_code,
67
+ headers=dict(resp.headers)
68
+ )
69
+
70
+ # POST request - forward to upstream
71
+ try:
72
+ upstream_resp = requests.post(
73
+ f"{UPSTREAM_URL}/mcp",
74
+ json=request.get_json(),
75
+ headers={
76
+ "Content-Type": "application/json",
77
+ "Accept": request.headers.get("Accept", "application/json"),
78
+ "X-Client-Name": request.headers.get("X-Client-Name", "unknown"),
79
+ },
80
+ timeout=30
81
+ )
82
+
83
+ # Try to parse and inject instructions
84
+ try:
85
+ data = upstream_resp.json()
86
+ data = inject_instructions(data)
87
+ return Response(
88
+ json.dumps(data),
89
+ status=upstream_resp.status_code,
90
+ content_type="application/json"
91
+ )
92
+ except json.JSONDecodeError:
93
+ # Not JSON, pass through as-is
94
+ return Response(
95
+ upstream_resp.content,
96
+ status=upstream_resp.status_code,
97
+ headers=dict(upstream_resp.headers)
98
+ )
99
+
100
+ except requests.exceptions.RequestException as e:
101
+ return Response(
102
+ json.dumps({"error": str(e)}),
103
+ status=502,
104
+ content_type="application/json"
105
+ )
106
+
107
+
108
+ @app.route("/health")
109
+ def health():
110
+ """Health check endpoint."""
111
+ return {"status": "ok", "service": "cybermem-instructions-injector"}
112
+
113
+
114
+ @app.route("/<path:path>", methods=["GET", "POST", "PUT", "DELETE", "PATCH"])
115
+ def proxy_other(path):
116
+ """Proxy all other requests to upstream without modification."""
117
+ resp = requests.request(
118
+ method=request.method,
119
+ url=f"{UPSTREAM_URL}/{path}",
120
+ headers={k: v for k, v in request.headers if k.lower() != "host"},
121
+ json=request.get_json() if request.is_json else None,
122
+ data=request.data if not request.is_json else None,
123
+ params=request.args,
124
+ timeout=30
125
+ )
126
+ return Response(
127
+ resp.content,
128
+ status=resp.status_code,
129
+ headers=dict(resp.headers)
130
+ )
131
+
132
+
133
+ if __name__ == "__main__":
134
+ port = int(os.environ.get("PORT", "8081"))
135
+ print(f"CyberMem Instructions Injector starting on port {port}")
136
+ print(f"Upstream: {UPSTREAM_URL}")
137
+ app.run(host="0.0.0.0", port=port)
@@ -0,0 +1,3 @@
1
+ flask>=2.3.0
2
+ requests>=2.31.0
3
+ gunicorn>=21.0.0
@@ -209,16 +209,20 @@ def parse_and_export():
209
209
  endpoint = path.split("?")[0]
210
210
 
211
211
  # Determine operation type from endpoint BEFORE normalization
212
- if endpoint == "/memory/add":
212
+ if endpoint == "/memory/add" or endpoint == "/add":
213
213
  operation = "create"
214
- elif endpoint == "/memory/query":
214
+ elif endpoint == "/memory/query" or endpoint == "/query":
215
215
  operation = "read"
216
+ elif endpoint == "/memory/all" or endpoint == "/all":
217
+ operation = "read" # list_memories
218
+ elif endpoint.startswith("/memory/") and method == "GET":
219
+ operation = "read" # get by ID
216
220
  elif endpoint.startswith("/memory/") and method == "PATCH":
217
221
  operation = "update"
218
222
  elif endpoint.startswith("/memory/") and method == "DELETE":
219
223
  operation = "delete"
220
224
  elif endpoint.startswith("/mcp"):
221
- operation = "create" # MCP operations are typically POST
225
+ operation = "mcp" # MCP operations tracked separately
222
226
  else:
223
227
  operation = "other"
224
228
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cybermem/cli",
3
- "version": "0.6.3",
3
+ "version": "0.6.7",
4
4
  "description": "CyberMem — Universal Long-Term Memory for AI Agents",
5
5
  "homepage": "https://cybermem.dev",
6
6
  "repository": {
@@ -79,7 +79,7 @@ services:
79
79
  labels:
80
80
  - traefik.enable=true
81
81
  - traefik.http.routers.openmemory.entrypoints=web
82
- - traefik.http.routers.openmemory.rule=PathPrefix(`/memory`) || PathPrefix(`/health`) || PathPrefix(`/v1`) || PathPrefix(`/api`) || PathPrefix(`/all`) || PathPrefix(`/add`) || PathPrefix(`/mcp`) || PathPrefix(`/sse`)
82
+ - traefik.http.routers.openmemory.rule=PathPrefix(`/memory`) || PathPrefix(`/health`) || PathPrefix(`/v1`) || PathPrefix(`/api`) || PathPrefix(`/all`) || PathPrefix(`/add`) || PathPrefix(`/sse`)
83
83
  - traefik.http.services.openmemory.loadbalancer.server.port=8080
84
84
  healthcheck:
85
85
  test:
@@ -210,6 +210,14 @@ services:
210
210
  - openmemory-data:/data
211
211
  - /var/run/docker.sock:/var/run/docker.sock
212
212
  - ${CYBERMEM_ENV_PATH}:/app/shared.env
213
+ labels:
214
+ - traefik.enable=true
215
+ # Dashboard route: /cybermem -> dashboard on port 3000
216
+ - traefik.http.routers.dashboard.entrypoints=web
217
+ - traefik.http.routers.dashboard.rule=PathPrefix(`/cybermem`)
218
+ - traefik.http.routers.dashboard.middlewares=strip-cybermem
219
+ - traefik.http.middlewares.strip-cybermem.stripprefix.prefixes=/cybermem
220
+ - traefik.http.services.dashboard.loadbalancer.server.port=3000
213
221
  restart: unless-stopped
214
222
  depends_on:
215
223
  - prometheus