@cybermem/cli 0.6.13 → 0.8.5

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.
@@ -97,7 +97,7 @@ function isLocalRequest(req) {
97
97
  }
98
98
 
99
99
  // ForwardAuth handler
100
- const server = http.createServer((req, res) => {
100
+ const server = http.createServer(async (req, res) => {
101
101
  // Health check
102
102
  if (req.url === "/health") {
103
103
  res.writeHead(200, { "Content-Type": "application/json" });
@@ -108,9 +108,22 @@ const server = http.createServer((req, res) => {
108
108
  const authHeader = req.headers["authorization"];
109
109
  const apiKeyHeader = req.headers["x-api-key"];
110
110
 
111
- // 1. Check JWT (Authorization: Bearer <token>)
111
+ // 1. Check Bearer token (JWT or API Key)
112
112
  if (authHeader?.startsWith("Bearer ")) {
113
113
  const token = authHeader.substring(7);
114
+
115
+ // 1a. Check if Bearer token is actually an API key (MCP clients like Claude Desktop)
116
+ const expectedKey = loadApiKey();
117
+ if (expectedKey && token === expectedKey) {
118
+ console.log("Auth OK: Bearer API Key");
119
+ res.writeHead(200, {
120
+ "X-Auth-Method": "bearer-api-key",
121
+ });
122
+ res.end();
123
+ return;
124
+ }
125
+
126
+ // 1b. Try JWT RS256 validation
114
127
  const payload = validateJwt(token);
115
128
 
116
129
  if (payload) {
@@ -124,6 +137,45 @@ const server = http.createServer((req, res) => {
124
137
  res.end();
125
138
  return;
126
139
  }
140
+
141
+ // 1c. Try GitHub OAuth token verification
142
+ try {
143
+ const https = require("https");
144
+ const ghRes = await new Promise((resolve, reject) => {
145
+ const req = https.get(
146
+ "https://api.github.com/user",
147
+ {
148
+ headers: {
149
+ Authorization: `Bearer ${token}`,
150
+ "User-Agent": "CyberMem-Auth-Sidecar/1.0",
151
+ Accept: "application/vnd.github+json",
152
+ },
153
+ },
154
+ (res) => {
155
+ let data = "";
156
+ res.on("data", (chunk) => (data += chunk));
157
+ res.on("end", () => resolve({ status: res.statusCode, data }));
158
+ },
159
+ );
160
+ req.on("error", reject);
161
+ req.end();
162
+ });
163
+
164
+ if (ghRes.status === 200) {
165
+ const user = JSON.parse(ghRes.data);
166
+ console.log(`Auth OK: GitHub OAuth (${user.login})`);
167
+ res.writeHead(200, {
168
+ "X-User-Id": String(user.id),
169
+ "X-User-Email": user.email || "",
170
+ "X-User-Name": user.login,
171
+ "X-Auth-Method": "github-oauth",
172
+ });
173
+ res.end();
174
+ return;
175
+ }
176
+ } catch (err) {
177
+ // GitHub verification failed, continue to other methods
178
+ }
127
179
  }
128
180
 
129
181
  // 2. Check API Key (deprecated fallback)
@@ -1,3 +1,4 @@
1
+ # yaml-language-server: $schema=https://raw.githubusercontent.com/compose-spec/compose-spec/master/schema/compose-spec.json
1
2
  # CyberMem - OpenMemory + DevOps monitoring stack
2
3
  # For local development only
3
4
  # Production deployment: use Helm chart (charts/cybermem/)
@@ -24,28 +25,53 @@ services:
24
25
  - traefik-logs:/var/log/traefik
25
26
  labels:
26
27
  - traefik.enable=true
28
+ - traefik.http.middlewares.auth-check.forwardauth.address=http://auth-sidecar:3001/auth
29
+ - traefik.http.middlewares.auth-check.forwardauth.authResponseHeaders=X-User-Id,X-User-Email,X-User-Name,X-Auth-Method
27
30
  restart: unless-stopped
28
31
 
29
32
  # MCP Server (Node.js/SDK)
30
33
  mcp-server:
34
+ build:
35
+ context: ../../mcp
36
+ dockerfile: Dockerfile
31
37
  image: ghcr.io/mikhailkogan17/cybermem-mcp:latest
38
+ # Note: platform omitted - docker auto-detects (amd64 for CI, arm64 for RPi)
32
39
  restart: unless-stopped
33
40
  container_name: cybermem-mcp
34
41
  environment:
42
+ OM_TIER: "hybrid"
35
43
  PORT: "8080"
44
+ OM_PORT: "0"
36
45
  OM_DB_PATH: /data/openmemory.sqlite
37
46
  volumes:
38
47
  - ${CYBERMEM_ENV_PATH:-${HOME}/.cybermem/.env}:/.env
39
48
  - ${HOME}/.cybermem/data:/data
49
+ depends_on:
50
+ - traefik
51
+ - auth-sidecar
40
52
  labels:
41
53
  - traefik.enable=true
42
- - traefik.http.routers.mcp.rule=Host(`raspberrypi.local`) && (PathPrefix(`/mcp`) || PathPrefix(`/sse`))
54
+ # Middleware definitions (must be on a service that starts BEFORE routes reference them)
55
+ - traefik.http.middlewares.strip-cybermem.stripprefix.prefixes=/cybermem
56
+ # Authenticated routes
57
+ - traefik.http.routers.mcp.rule=PathPrefix(`/cybermem/mcp`) || PathPrefix(`/cybermem/sse`) || PathPrefix(`/mcp`) || PathPrefix(`/sse`)
43
58
  - traefik.http.routers.mcp.entrypoints=web
44
- - traefik.http.services.mcp.loadbalancer.server.port=8080
59
+ - traefik.http.routers.mcp.priority=100
60
+ - traefik.http.routers.mcp.service=mcp-service
61
+ - traefik.http.routers.mcp.middlewares=auth-check,strip-cybermem
62
+ # Public routes (Health/Metrics)
63
+ - traefik.http.routers.mcp-public.rule=PathPrefix(`/cybermem/health`) || PathPrefix(`/cybermem/metrics`) || PathPrefix(`/health`) || PathPrefix(`/metrics`)
64
+ - traefik.http.routers.mcp-public.entrypoints=web
65
+ - traefik.http.routers.mcp-public.priority=100
66
+ - traefik.http.routers.mcp-public.service=mcp-service
67
+ - traefik.http.routers.mcp-public.middlewares=strip-cybermem
68
+ - traefik.http.services.mcp-service.loadbalancer.server.port=8080
45
69
  # Legacy API support (for simple REST clients)
46
- - traefik.http.routers.legacy-api.rule=Host(`raspberrypi.local`) && (PathPrefix(`/add`) || PathPrefix(`/query`) || PathPrefix(`/all`))
70
+ - traefik.http.routers.legacy-api.rule=PathPrefix(`/cybermem/add`) || PathPrefix(`/cybermem/query`) || PathPrefix(`/cybermem/all`) || PathPrefix(`/add`) || PathPrefix(`/query`) || PathPrefix(`/all`)
47
71
  - traefik.http.routers.legacy-api.entrypoints=web
48
- - traefik.http.services.legacy-api.loadbalancer.server.port=8080
72
+ - traefik.http.routers.legacy-api.priority=100
73
+ - traefik.http.routers.legacy-api.service=mcp-service
74
+ - traefik.http.routers.legacy-api.middlewares=auth-check,strip-cybermem
49
75
 
50
76
  # Auth sidecar for JWT/API key validation (ForwardAuth)
51
77
  auth-sidecar:
@@ -58,9 +84,6 @@ services:
58
84
  - ${CYBERMEM_ENV_PATH:-${HOME}/.cybermem/.env}:/.env:ro
59
85
  labels:
60
86
  - traefik.enable=true
61
- # ForwardAuth middleware
62
- - traefik.http.middlewares.auth-check.forwardauth.address=http://auth-sidecar:3001/auth
63
- - traefik.http.middlewares.auth-check.forwardauth.authResponseHeaders=X-User-Id,X-User-Email,X-User-Name,X-Auth-Method
64
87
  healthcheck:
65
88
  test:
66
89
  [
@@ -76,9 +99,8 @@ services:
76
99
  retries: 3
77
100
  restart: unless-stopped
78
101
 
79
- # NOTE: openmemory container REMOVED
80
- # Memory now handled by @cybermem/mcp with embedded openmemory-js SDK
81
- # SQLite stored at ~/.cybermem/data/openmemory.sqlite
102
+ # Memory engine (MCP + Persistence)
103
+ # Uses embedded SQLite stored at ~/.cybermem/data/openmemory.sqlite
82
104
 
83
105
  db-exporter:
84
106
  image: ghcr.io/mikhailkogan17/cybermem-db_exporter:latest
@@ -130,22 +152,6 @@ services:
130
152
  profiles:
131
153
  - postgres
132
154
 
133
- postgres-exporter:
134
- image: prometheuscommunity/postgres-exporter:v0.15.0
135
- container_name: cybermem-postgres-exporter
136
- environment:
137
- DATA_SOURCE_NAME: postgresql://${PG_USER:-openmemory}:${PG_PASSWORD:-postgres}@postgres:5432/${PG_DB:-openmemory}?sslmode=disable
138
- PG_EXPORTER_EXTEND_QUERY_PATH: /queries.yml
139
- ports:
140
- - "9187:9187"
141
- volumes:
142
- - ./monitoring/postgres_exporter/queries.yml:/queries.yml:ro
143
- restart: unless-stopped
144
- depends_on:
145
- - postgres
146
- profiles:
147
- - postgres
148
-
149
155
  ollama:
150
156
  image: ollama/ollama:latest
151
157
  container_name: cybermem-ollama
@@ -157,55 +163,37 @@ services:
157
163
  profiles:
158
164
  - ollama
159
165
 
160
- prometheus:
161
- image: prom/prometheus:v2.48.0
162
- container_name: cybermem-prometheus
163
- command:
164
- - --config.file=/etc/prometheus/prometheus.yml
165
- - --storage.tsdb.path=/prometheus
166
- - --storage.tsdb.retention.time=${PROM_RETENTION:-7d}
167
- - --web.console.libraries=/usr/share/prometheus/console_libraries
168
- - --web.console.templates=/usr/share/prometheus/consoles
169
- ports:
170
- - "9092:9090"
171
- volumes:
172
- - ./monitoring/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
173
- - prometheus-data:/prometheus
174
- restart: unless-stopped
175
- depends_on:
176
- - db-exporter
177
-
178
166
  dashboard:
179
167
  image: ghcr.io/mikhailkogan17/cybermem-dashboard:latest
168
+ # Note: platform omitted - docker auto-detects (amd64 for CI, arm64 for RPi)
180
169
  container_name: cybermem-dashboard
181
170
  environment:
182
171
  DB_EXPORTER_URL: http://db-exporter:8000
183
- NEXT_PUBLIC_PROMETHEUS_URL: http://prometheus:9090
184
- PROMETHEUS_URL: http://prometheus:9090
172
+ # Required for Health Check to find the MCP API internally
173
+ CYBERMEM_URL: http://mcp-server:8080
174
+ OM_DB_PATH: /data/openmemory.sqlite
185
175
  OM_API_KEY: ${OM_API_KEY:-dev-secret-key}
186
- # WATCHPACK_POLLING: "true" # Enable if hot reload blocks (high CPU usage)
187
176
  ports:
188
177
  - "3000:3000"
189
178
  volumes:
190
- - openmemory-data:/data
179
+ - ${HOME}/.cybermem/data:/data
191
180
  - /var/run/docker.sock:/var/run/docker.sock
192
181
  - ${CYBERMEM_ENV_PATH:-${HOME}/.cybermem/.env}:/app/shared.env
193
182
  labels:
194
183
  - traefik.enable=true
195
184
  # Dashboard route: /cybermem -> dashboard on port 3000
196
185
  - traefik.http.routers.dashboard.entrypoints=web
197
- - traefik.http.routers.dashboard.rule=PathPrefix(`/cybermem`)
198
- - traefik.http.routers.dashboard.middlewares=strip-cybermem
199
- - traefik.http.middlewares.strip-cybermem.stripprefix.prefixes=/cybermem
186
+ - traefik.http.routers.dashboard.rule=PathPrefix(`/cybermem`) && !PathPrefix(`/cybermem/mcp`) && !PathPrefix(`/cybermem/health`)
187
+ - traefik.http.routers.dashboard.priority=10
188
+ - traefik.http.routers.dashboard.middlewares=auth-check,strip-cybermem
200
189
  - traefik.http.services.dashboard.loadbalancer.server.port=3000
201
190
  restart: unless-stopped
202
191
  depends_on:
203
- - prometheus
204
192
  - db-exporter
205
193
 
206
194
  volumes:
207
195
  openmemory-data:
208
- name: cybermem-openmemory-data
196
+ name: cybermem-data
209
197
  driver: local
210
198
  postgres-data:
211
199
  name: cybermem-postgres-data
@@ -213,9 +201,6 @@ volumes:
213
201
  ollama-models:
214
202
  name: cybermem-ollama-models
215
203
  driver: local
216
- prometheus-data:
217
- name: cybermem-prometheus-data
218
- driver: local
219
204
  traefik-logs:
220
205
  name: cybermem-traefik-logs
221
206
  driver: local
@@ -0,0 +1,17 @@
1
+ # Environment: LOCAL (Dev)
2
+ # No API key required for localhost access.
3
+ # Used by: npx @cybermem/cli init --local
4
+
5
+ # Application Settings
6
+ LOG_LEVEL=info
7
+ NEXT_PUBLIC_APP_URL=http://localhost:3000
8
+
9
+ # Database (SQLite)
10
+ # OM_DB_PATH is mounted to /data/openmemory.sqlite in Docker
11
+
12
+ # Security
13
+ # OM_API_KEY is intentionally empty for local development to bypass auth.
14
+ OM_API_KEY=
15
+
16
+ # Optional: Add your OpenAI Key if using cloud embeddings
17
+ # OPENAI_API_KEY=
@@ -0,0 +1,23 @@
1
+ # Environment: RPi + Tailscale (Remote Access)
2
+ # Secured via Tailscale Funnel.
3
+ # Used by: npx @cybermem/cli init --rpi --remote-access
4
+
5
+ # Application Settings
6
+ LOG_LEVEL=info
7
+
8
+ # Database (SQLite)
9
+ # OM_DB_PATH is mounted to /data/openmemory.sqlite in Docker
10
+
11
+ # Security
12
+ # GENERATED AUTOMATICALLY BY CLI
13
+ OM_API_KEY=
14
+
15
+ # Tailscale Configuration
16
+ # Ensure "tailscale serve" is running (handled by CLI)
17
+ TS_HOSTNAME=raspberrypi
18
+
19
+ # Dashboard
20
+ NEXT_PUBLIC_APP_URL=https://raspberrypi.tailnet-name.ts.net
21
+
22
+ # Optional: Local LLM
23
+ OLLAMA_URL=http://ollama:11434
@@ -0,0 +1,22 @@
1
+ # Environment: RPi (Local Network)
2
+ # Internal API key required.
3
+ # Used by: npx @cybermem/cli init --rpi
4
+
5
+ # Application Settings
6
+ LOG_LEVEL=info
7
+
8
+ # Database (SQLite)
9
+ # OM_DB_PATH is mounted to /data/openmemory.sqlite in Docker
10
+
11
+ # Security
12
+ # GENERATED AUTOMATICALLY BY CLI
13
+ # DO NOT SHARE THIS KEY. It is for internal service communication.
14
+ OM_API_KEY=
15
+
16
+ # Dashboard
17
+ # Clients connecting via MCP must use OAuth or this key (if configured for direct access).
18
+ # OAUTH_CLIENT_ID=...
19
+ # OAUTH_CLIENT_SECRET=...
20
+
21
+ # Optional: Local LLM
22
+ OLLAMA_URL=http://ollama:11434
@@ -0,0 +1,29 @@
1
+ # Environment: VPS (Public Cloud)
2
+ # High security setup.
3
+ # Used by: npx @cybermem/cli init --vps
4
+
5
+ # Application Settings
6
+ LOG_LEVEL=warn
7
+ # Set your domain here
8
+ NEXT_PUBLIC_APP_URL=https://cybermem.yourdomain.com
9
+
10
+ # Database (PostgreSQL recommended for VPS, but SQLite default)
11
+ # To use Postgres:
12
+ # DB_TYPE=postgres
13
+ # POSTGRES_URL=postgresql://user:pass@db:5432/cybermem
14
+
15
+ # Security
16
+ # GENERATED AUTOMATICALLY BY CLI
17
+ OM_API_KEY=
18
+
19
+ # Auth Sidecar (JWT)
20
+ # Generate a random secret: openssl rand -hex 32
21
+ JWT_SECRET=
22
+ JWT_PRIVATE_KEY=
23
+
24
+ # GitHub OAuth (Recommended for dashboard access)
25
+ OAUTH_CLIENT_ID=
26
+ OAUTH_CLIENT_SECRET=
27
+
28
+ # OpenAI (Recommended for VPS performance)
29
+ OPENAI_API_KEY=
@@ -123,11 +123,16 @@ def collect_metrics():
123
123
  FROM memories
124
124
  GROUP BY user_id
125
125
  """)
126
- for row in cursor.fetchall():
127
- client = row["client"] or "anonymous"
128
- memories_total.labels(client=client).set(row["count"])
126
+ rows = cursor.fetchall()
127
+ if not rows:
128
+ # Emit a default anonymous:0 if no data
129
+ memories_total.labels(client="anonymous").set(0)
130
+ else:
131
+ for row in rows:
132
+ client = row["client"] or "anonymous"
133
+ memories_total.labels(client=client).set(row["count"])
129
134
 
130
- logger.debug(f"Collected total memories for {cursor.rowcount} clients")
135
+ logger.debug(f"Collected total memories for {len(rows)} clients")
131
136
 
132
137
  # Metric 2: Recent memories (24h)
133
138
  # Note: created_at is stored as milliseconds since epoch
@@ -1,27 +0,0 @@
1
- # Example environment configuration
2
- # Copy to .env and customize
3
-
4
- # Embeddings provider: ollama (local) or openai (cloud)
5
- EMBEDDINGS_PROVIDER=ollama
6
- OLLAMA_URL=http://ollama:11434
7
- OPENAI_API_KEY=
8
-
9
- # Database backend: sqlite (local/rpi) or postgres (vps)
10
- DB_BACKEND=sqlite
11
- DB_PATH=/data/openmemory.sqlite
12
- VECTOR_BACKEND=sqlite
13
-
14
- # PostgreSQL settings (only for DB_BACKEND=postgres)
15
- PG_HOST=postgres
16
- PG_PORT=5432
17
- PG_DB=openmemory
18
- PG_USER=openmemory
19
- PG_PASSWORD=change-me
20
-
21
- # OpenMemory API key (Optional for local mode)
22
- # OM_API_KEY=
23
-
24
- # Monitoring
25
- PROM_RETENTION=7d
26
- GRAFANA_USER=admin
27
- GRAFANA_PASSWORD=admin
@@ -1,27 +0,0 @@
1
- # Raspberry Pi environment
2
- # Optimized for low memory (1GB total)
3
- DOCKER_PLATFORM=linux/arm64
4
-
5
-
6
- # Embeddings (use Ollama with small models)
7
- EMBEDDINGS_PROVIDER=ollama
8
- OLLAMA_URL=http://ollama:11434
9
- OPENAI_API_KEY=
10
-
11
- # Database (SQLite for RPi - low memory footprint)
12
- DB_BACKEND=sqlite
13
- DB_PATH=/data/openmemory.sqlite
14
-
15
- # PostgreSQL (not used)
16
- PG_HOST=postgres
17
- PG_DB=openmemory
18
- PG_USER=openmemory
19
- PG_PASSWORD=not-used
20
-
21
- # OpenMemory
22
- OM_API_KEY=key-change-me
23
-
24
- # Monitoring (short retention for disk space)
25
- PROM_RETENTION=3d
26
- GRAFANA_USER=admin
27
- GRAFANA_PASSWORD=admin
@@ -1,25 +0,0 @@
1
- # VPS production environment
2
- # For Hetzner CX22 or similar (2 vCPU, 4GB RAM)
3
-
4
- # Embeddings (use OpenAI for production)
5
- EMBEDDINGS_PROVIDER=openai
6
- OPENAI_API_KEY=sk-change-me-in-production
7
- OLLAMA_URL=
8
-
9
- # Database (PostgreSQL for production)
10
- DB_BACKEND=postgres
11
- VECTOR_BACKEND=postgres
12
-
13
- # PostgreSQL
14
- PG_HOST=postgres
15
- PG_DB=openmemory
16
- PG_USER=openmemory
17
- PG_PASSWORD=change-me-in-production-use-secrets
18
-
19
- # OpenMemory
20
- OM_API_KEY=change-me-in-production-use-secrets
21
-
22
- # Monitoring
23
- PROM_RETENTION=30d
24
- GRAFANA_USER=admin
25
- GRAFANA_PASSWORD=change-me-in-production-use-secrets