@cybermem/cli 0.6.13 → 0.7.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,141 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.upgrade = upgrade;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const execa_1 = __importDefault(require("execa"));
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const inquirer_1 = __importDefault(require("inquirer"));
11
+ const os_1 = __importDefault(require("os"));
12
+ const path_1 = __importDefault(require("path"));
13
+ async function upgrade(options) {
14
+ let target = "local";
15
+ if (options.rpi)
16
+ target = "rpi";
17
+ if (options.vps)
18
+ target = "vps";
19
+ console.log(chalk_1.default.blue(`Upgrading CyberMem (${target})...`));
20
+ try {
21
+ if (target === "local") {
22
+ console.log(chalk_1.default.blue("Pulling latest Docker images..."));
23
+ // Re-use logic: find compose file from template if needed, OR use installed ~/.cybermem one?
24
+ // "upgrade" implies updating running instance.
25
+ // running instance uses ~/.cybermem/docker-compose.yml (if init copied it?)
26
+ // Wait, init uses template directly?
27
+ // "deploy/init" logic for local:
28
+ // "docker-compose -f composeFile ...". composeFile was from templateDir.
29
+ // It did NOT copy compose file to ~/.cybermem for local.
30
+ // It uses the installed package's template.
31
+ // So for upgrade (which might be run from newer CLI version), we use the NEW CLI's template.
32
+ // Resolve Template Directory (Same logic as init)
33
+ let templateDir = path_1.default.resolve(__dirname, "../../templates");
34
+ if (!fs_1.default.existsSync(templateDir)) {
35
+ templateDir = path_1.default.resolve(__dirname, "../../../templates");
36
+ }
37
+ if (!fs_1.default.existsSync(templateDir)) {
38
+ templateDir = path_1.default.resolve(process.cwd(), "packages/cli/templates");
39
+ }
40
+ if (!fs_1.default.existsSync(templateDir)) {
41
+ templateDir = path_1.default.resolve(__dirname, "../templates");
42
+ }
43
+ if (!fs_1.default.existsSync(templateDir)) {
44
+ throw new Error(`Templates not found at ${templateDir}.`);
45
+ }
46
+ const composeFile = path_1.default.join(templateDir, "docker-compose.yml");
47
+ const homeDir = os_1.default.homedir();
48
+ const configDir = path_1.default.join(homeDir, ".cybermem");
49
+ const envFile = path_1.default.join(configDir, ".env");
50
+ const dataDir = path_1.default.join(configDir, "data");
51
+ // Pull images
52
+ await (0, execa_1.default)("docker-compose", [
53
+ "-f",
54
+ composeFile,
55
+ "--env-file",
56
+ envFile,
57
+ "--project-name",
58
+ "cybermem",
59
+ "pull",
60
+ ], {
61
+ stdio: "inherit",
62
+ env: {
63
+ ...process.env,
64
+ DATA_DIR: dataDir,
65
+ CYBERMEM_ENV_PATH: envFile,
66
+ OM_API_KEY: "", // Local bypass
67
+ },
68
+ });
69
+ // Up (recreate)
70
+ console.log(chalk_1.default.blue("Restarting services..."));
71
+ await (0, execa_1.default)("docker-compose", [
72
+ "-f",
73
+ composeFile,
74
+ "--env-file",
75
+ envFile,
76
+ "--project-name",
77
+ "cybermem",
78
+ "up",
79
+ "-d",
80
+ "--remove-orphans",
81
+ ], {
82
+ stdio: "inherit",
83
+ env: {
84
+ ...process.env,
85
+ DATA_DIR: dataDir,
86
+ CYBERMEM_ENV_PATH: envFile,
87
+ OM_API_KEY: "", // Local bypass
88
+ },
89
+ });
90
+ console.log(chalk_1.default.green("✅ Upgrade complete!"));
91
+ }
92
+ else if (target === "rpi" || target === "vps") {
93
+ // Remote upgrade
94
+ let sshHost = options.host;
95
+ if (!sshHost) {
96
+ const answers = await inquirer_1.default.prompt([
97
+ {
98
+ type: "input",
99
+ name: "host",
100
+ message: "Enter SSH Host (e.g. pi@raspberrypi.local):",
101
+ validate: (input) => input.includes("@") ? true : "Format must be user@host",
102
+ },
103
+ ]);
104
+ sshHost = answers.host;
105
+ }
106
+ console.log(chalk_1.default.blue(`Upgrading remote host ${sshHost}...`));
107
+ // 1. Upload NEW docker-compose.yml (from this CLI version)
108
+ // Resolve Template Directory
109
+ let templateDir = path_1.default.resolve(__dirname, "../../templates");
110
+ if (!fs_1.default.existsSync(templateDir)) {
111
+ templateDir = path_1.default.resolve(__dirname, "../../../templates");
112
+ }
113
+ if (!fs_1.default.existsSync(templateDir)) {
114
+ templateDir = path_1.default.resolve(process.cwd(), "packages/cli/templates");
115
+ }
116
+ if (!fs_1.default.existsSync(templateDir)) {
117
+ templateDir = path_1.default.resolve(__dirname, "../templates");
118
+ }
119
+ const composeFile = path_1.default.join(templateDir, "docker-compose.yml");
120
+ console.log(chalk_1.default.blue("Uploading newest definitions..."));
121
+ await (0, execa_1.default)("scp", [
122
+ composeFile,
123
+ `${sshHost}:~/.cybermem/docker-compose.yml`,
124
+ ]);
125
+ // 2. Pull and Up on Remote
126
+ const remoteCmd = `
127
+ export CYBERMEM_ENV_PATH=~/.cybermem/.env
128
+ export DATA_DIR=~/.cybermem/data
129
+ export DOCKER_DEFAULT_PLATFORM=linux/arm64
130
+ docker-compose -f ~/.cybermem/docker-compose.yml pull
131
+ docker-compose -f ~/.cybermem/docker-compose.yml up -d --remove-orphans
132
+ `;
133
+ await (0, execa_1.default)("ssh", [sshHost, remoteCmd], { stdio: "inherit" });
134
+ console.log(chalk_1.default.green("✅ Remote upgrade complete!"));
135
+ }
136
+ }
137
+ catch (error) {
138
+ console.error(chalk_1.default.red("Upgrade failed:"), error);
139
+ process.exit(1);
140
+ }
141
+ }
package/dist/index.js CHANGED
@@ -3,33 +3,54 @@
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  const commander_1 = require("commander");
5
5
  const backup_1 = require("./commands/backup");
6
- const deploy_1 = require("./commands/deploy");
6
+ const dashboard_1 = require("./commands/dashboard");
7
+ const init_1 = require("./commands/init");
8
+ const login_1 = require("./commands/login");
7
9
  const reset_1 = require("./commands/reset");
8
10
  const restore_1 = require("./commands/restore");
11
+ const upgrade_1 = require("./commands/upgrade");
9
12
  const program = new commander_1.Command();
10
13
  program
11
- .name('mcp')
12
- .description('CyberMem - Deploy your AI memory server in one command')
13
- .version('1.0.0');
14
- // Default Command: Deploy
14
+ .name("mcp")
15
+ .description("CyberMem - Deploy your AI memory server in one command")
16
+ .version("1.0.0");
17
+ // Command: Login
15
18
  program
16
- .command('deploy', { isDefault: true })
17
- .description('Deploy CyberMem (Default)')
18
- .option('--rpi', 'Deploy to Raspberry Pi (default: local)')
19
- .option('--vps', 'Deploy to VPS/Cloud server')
20
- .option('--remote-access', 'Enable Tailscale Funnel for HTTPS remote access')
21
- .action(deploy_1.deploy);
19
+ .command("login")
20
+ .description("Login to CyberMem via GitHub (OAuth)")
21
+ .action(login_1.login);
22
+ // Command: Init (formerly deploy)
22
23
  program
23
- .command('backup')
24
- .description('Backup CyberMem data to a tarball')
24
+ .command("init")
25
+ .description("Initialize CyberMem (Scaffold & Start)")
26
+ .option("--rpi", "Deploy to Raspberry Pi")
27
+ .option("--vps", "Deploy to VPS/Cloud server")
28
+ .option("--remote-access", "Enable Tailscale Funnel for HTTPS remote access")
29
+ .action(init_1.init);
30
+ // Command: Upgrade
31
+ program
32
+ .command("upgrade")
33
+ .description("Upgrade CyberMem instance (pull latest images)")
34
+ .option("--local", "Upgrade local instance (default)")
35
+ .option("--rpi", "Upgrade remote RPi")
36
+ .option("--vps", "Upgrade remote VPS")
37
+ .option("--host <host>", "SSH host for remote upgrade (e.g. pi@raspberrypi.local)")
38
+ .action(upgrade_1.upgrade);
39
+ program
40
+ .command("backup")
41
+ .description("Backup CyberMem data to a tarball")
25
42
  .action(backup_1.backup);
26
43
  program
27
- .command('restore')
28
- .description('Restore CyberMem data from a backup file')
29
- .argument('<file>', 'Backup file to restore')
44
+ .command("restore")
45
+ .description("Restore CyberMem data from a backup file")
46
+ .argument("<file>", "Backup file to restore")
30
47
  .action(restore_1.restore);
31
48
  program
32
- .command('reset')
33
- .description('Reset (wipe) the CyberMem database - DESTRUCTIVE!')
49
+ .command("reset")
50
+ .description("Reset (wipe) the CyberMem database - DESTRUCTIVE!")
34
51
  .action(reset_1.reset);
52
+ program
53
+ .command("dashboard")
54
+ .description("Open the CyberMem dashboard and check stack status")
55
+ .action(dashboard_1.dashboard);
35
56
  program.parse(process.argv);
@@ -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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cybermem/cli",
3
- "version": "0.6.13",
3
+ "version": "0.7.7",
4
4
  "description": "CyberMem — Universal Long-Term Memory for AI Agents",
5
5
  "homepage": "https://cybermem.dev",
6
6
  "repository": {
@@ -17,6 +17,7 @@
17
17
  ],
18
18
  "scripts": {
19
19
  "build": "tsc && cp -r templates dist/",
20
+ "lint": "eslint src/ --ext .ts",
20
21
  "start": "ts-node src/index.ts",
21
22
  "test:e2e": "ts-node e2e/test-mcp.ts",
22
23
  "prepublishOnly": "npm run build"
@@ -34,16 +35,21 @@
34
35
  },
35
36
  "type": "commonjs",
36
37
  "dependencies": {
38
+ "@types/open": "^6.1.0",
37
39
  "chalk": "^4.1.2",
38
40
  "commander": "^9.0.0",
39
41
  "dotenv": "^16.0.0",
40
42
  "execa": "^5.1.1",
41
43
  "inquirer": "^8.2.0",
44
+ "open": "^11.0.0",
42
45
  "ora": "^5.4.1"
43
46
  },
44
47
  "devDependencies": {
45
48
  "@types/inquirer": "^9.0.3",
46
49
  "@types/node": "^18.0.0",
50
+ "@typescript-eslint/eslint-plugin": "^8.52.0",
51
+ "@typescript-eslint/parser": "^8.52.0",
52
+ "eslint": "^9.39.2",
47
53
  "ts-node": "^10.9.1",
48
54
  "typescript": "^5.0.0"
49
55
  }