@cybermem/cli 0.9.12 → 0.13.3

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.
Files changed (56) hide show
  1. package/dist/commands/install.js +430 -0
  2. package/dist/commands/reset.js +18 -2
  3. package/dist/commands/uninstall.js +145 -0
  4. package/dist/commands/upgrade.js +91 -52
  5. package/dist/index.js +18 -5
  6. package/dist/templates/ansible/playbooks/deploy-cybermem.yml +201 -24
  7. package/dist/templates/ansible/playbooks/reset-db.yml +44 -0
  8. package/dist/templates/auth-sidecar/Dockerfile +2 -10
  9. package/dist/templates/auth-sidecar/package.json +1 -3
  10. package/dist/templates/auth-sidecar/server.js +149 -110
  11. package/dist/templates/charts/cybermem/.helmignore +13 -0
  12. package/dist/templates/charts/cybermem/templates/dashboard-deployment.yaml +31 -7
  13. package/dist/templates/charts/cybermem/templates/dashboard-service.yaml +4 -4
  14. package/dist/templates/charts/cybermem/templates/openmemory-deployment.yaml +14 -8
  15. package/dist/templates/charts/cybermem/templates/openmemory-service.yaml +3 -3
  16. package/dist/templates/charts/cybermem/templates/secret.yaml +9 -0
  17. package/dist/templates/charts/cybermem/templates/traefik-config.yaml +67 -0
  18. package/dist/templates/charts/cybermem/templates/traefik-deployment.yaml +53 -0
  19. package/dist/templates/charts/cybermem/templates/traefik-service.yaml +17 -0
  20. package/dist/templates/charts/cybermem/values-vps.yaml +8 -4
  21. package/dist/templates/charts/cybermem/values.yaml +17 -9
  22. package/dist/templates/docker-compose.yml +103 -78
  23. package/dist/templates/monitoring/log_exporter/exporter.py +22 -29
  24. package/dist/templates/monitoring/traefik/traefik.yml +1 -4
  25. package/package.json +9 -3
  26. package/templates/ansible/playbooks/deploy-cybermem.yml +201 -24
  27. package/templates/ansible/playbooks/reset-db.yml +44 -0
  28. package/templates/auth-sidecar/Dockerfile +2 -10
  29. package/templates/auth-sidecar/package.json +1 -3
  30. package/templates/auth-sidecar/server.js +149 -110
  31. package/templates/charts/cybermem/.helmignore +13 -0
  32. package/templates/charts/cybermem/templates/dashboard-deployment.yaml +31 -7
  33. package/templates/charts/cybermem/templates/dashboard-service.yaml +4 -4
  34. package/templates/charts/cybermem/templates/openmemory-deployment.yaml +14 -8
  35. package/templates/charts/cybermem/templates/openmemory-service.yaml +3 -3
  36. package/templates/charts/cybermem/templates/secret.yaml +9 -0
  37. package/templates/charts/cybermem/templates/traefik-config.yaml +67 -0
  38. package/templates/charts/cybermem/templates/traefik-deployment.yaml +53 -0
  39. package/templates/charts/cybermem/templates/traefik-service.yaml +17 -0
  40. package/templates/charts/cybermem/values-vps.yaml +8 -4
  41. package/templates/charts/cybermem/values.yaml +17 -9
  42. package/templates/docker-compose.yml +103 -78
  43. package/templates/monitoring/log_exporter/exporter.py +22 -29
  44. package/templates/monitoring/traefik/traefik.yml +1 -4
  45. package/dist/commands/__tests__/backup.test.js +0 -75
  46. package/dist/commands/__tests__/restore.test.js +0 -70
  47. package/dist/commands/deploy.js +0 -239
  48. package/dist/commands/init.js +0 -362
  49. package/dist/commands/login.js +0 -165
  50. package/dist/templates/envs/local.example +0 -27
  51. package/dist/templates/envs/rpi.example +0 -27
  52. package/dist/templates/envs/vps.example +0 -25
  53. package/dist/templates/monitoring/instructions_injector/Dockerfile +0 -15
  54. package/dist/templates/monitoring/instructions_injector/injector.py +0 -137
  55. package/dist/templates/monitoring/instructions_injector/requirements.txt +0 -3
  56. package/dist/templates/openmemory/Dockerfile +0 -19
@@ -1,165 +0,0 @@
1
- "use strict";
2
- /**
3
- * CyberMem CLI Auth Module
4
- *
5
- * Token storage and browser-based OAuth login flow.
6
- */
7
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
- if (k2 === undefined) k2 = k;
9
- var desc = Object.getOwnPropertyDescriptor(m, k);
10
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
- desc = { enumerable: true, get: function() { return m[k]; } };
12
- }
13
- Object.defineProperty(o, k2, desc);
14
- }) : (function(o, m, k, k2) {
15
- if (k2 === undefined) k2 = k;
16
- o[k2] = m[k];
17
- }));
18
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
- Object.defineProperty(o, "default", { enumerable: true, value: v });
20
- }) : function(o, v) {
21
- o["default"] = v;
22
- });
23
- var __importStar = (this && this.__importStar) || (function () {
24
- var ownKeys = function(o) {
25
- ownKeys = Object.getOwnPropertyNames || function (o) {
26
- var ar = [];
27
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
- return ar;
29
- };
30
- return ownKeys(o);
31
- };
32
- return function (mod) {
33
- if (mod && mod.__esModule) return mod;
34
- var result = {};
35
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
- __setModuleDefault(result, mod);
37
- return result;
38
- };
39
- })();
40
- var __importDefault = (this && this.__importDefault) || function (mod) {
41
- return (mod && mod.__esModule) ? mod : { "default": mod };
42
- };
43
- Object.defineProperty(exports, "__esModule", { value: true });
44
- exports.login = login;
45
- const chalk_1 = __importDefault(require("chalk"));
46
- const fs = __importStar(require("fs"));
47
- const http = __importStar(require("http"));
48
- const os = __importStar(require("os"));
49
- const path = __importStar(require("path"));
50
- const AUTH_DIR = path.join(os.homedir(), ".cybermem");
51
- const TOKEN_FILE = path.join(AUTH_DIR, "token.json");
52
- const AUTH_URL = process.env.CYBERMEM_AUTH_URL || "https://cybermem.dev";
53
- /**
54
- * Ensure the .cybermem directory exists
55
- */
56
- function ensureAuthDir() {
57
- if (!fs.existsSync(AUTH_DIR)) {
58
- fs.mkdirSync(AUTH_DIR, { recursive: true, mode: 0o700 });
59
- }
60
- }
61
- /**
62
- * Save token to disk
63
- */
64
- function saveToken(token, expiresIn, email, name) {
65
- ensureAuthDir();
66
- const expiresAt = new Date(Date.now() + expiresIn * 1000);
67
- const data = {
68
- access_token: token,
69
- expires_at: expiresAt.toISOString(),
70
- email,
71
- name,
72
- };
73
- fs.writeFileSync(TOKEN_FILE, JSON.stringify(data, null, 2), { mode: 0o600 });
74
- }
75
- /**
76
- * Start OAuth login flow
77
- * Opens browser and waits for callback with token
78
- */
79
- async function login() {
80
- return new Promise((resolve, reject) => {
81
- // Find available port
82
- const server = http.createServer();
83
- server.listen(0, "127.0.0.1", () => {
84
- const address = server.address();
85
- if (!address || typeof address === "string") {
86
- reject(new Error("Failed to start callback server"));
87
- return;
88
- }
89
- const port = address.port;
90
- const callbackUrl = `http://localhost:${port}/callback`;
91
- // Redirect to landing auth endpoint which starts GitHub flow
92
- // We pass our local callbackUrl as 'redirect' param to the intermediate CLI callback handler
93
- const authUrl = `${AUTH_URL}/api/auth/signin?callbackUrl=${encodeURIComponent(`${AUTH_URL}/api/auth/cli/callback?redirect=${encodeURIComponent(callbackUrl)}`)}`;
94
- console.log(chalk_1.default.blue("🔐 Opening browser for GitHub login..."));
95
- console.log(chalk_1.default.gray(` If browser doesn't open, visit: ${authUrl}`));
96
- // Open browser
97
- const open = async (url) => {
98
- const { default: openBrowser } = await Promise.resolve().then(() => __importStar(require("open")));
99
- await openBrowser(url);
100
- };
101
- open(authUrl);
102
- // Handle callback
103
- server.on("request", async (req, res) => {
104
- if (!req.url?.startsWith("/callback")) {
105
- res.writeHead(404);
106
- res.end("Not found");
107
- return;
108
- }
109
- const url = new URL(req.url, `http://localhost:${port}`);
110
- const token = url.searchParams.get("token");
111
- if (!token) {
112
- res.writeHead(400);
113
- res.end("Missing token");
114
- server.close();
115
- reject(new Error("No token received"));
116
- return;
117
- }
118
- // Decode token to get user info (JWT payload)
119
- let email;
120
- let name;
121
- try {
122
- const payload = JSON.parse(Buffer.from(token.split(".")[1], "base64").toString());
123
- email = payload.email;
124
- name = payload.name;
125
- }
126
- catch {
127
- // Ignore decode errors
128
- }
129
- // Save token (30 days expiry)
130
- saveToken(token, 30 * 24 * 60 * 60, email, name);
131
- // Send success page
132
- res.writeHead(200, { "Content-Type": "text/html" });
133
- res.end(`
134
- <!DOCTYPE html>
135
- <html>
136
- <head>
137
- <title>CyberMem - Logged In</title>
138
- <style>
139
- body { font-family: system-ui; text-align: center; padding: 50px; background: #0a0a0a; color: #fff; }
140
- h1 { color: #22c55e; }
141
- .logo { font-size: 48px; margin-bottom: 20px; }
142
- </style>
143
- </head>
144
- <body>
145
- <div class="logo">🧠</div>
146
- <h1>Successfully Logged In!</h1>
147
- <p>You can close this window and return to your terminal.</p>
148
- <p style="color: #888;">Logged in as: ${email || name || "Unknown"}</p>
149
- </body>
150
- </html>
151
- `);
152
- console.log("");
153
- console.log(chalk_1.default.green("✅ Successfully logged in as:"), chalk_1.default.bold(email || name || "Unknown"));
154
- console.log(chalk_1.default.gray(` Token saved to: ${TOKEN_FILE}`));
155
- server.close();
156
- resolve();
157
- });
158
- // Timeout after 5 minutes
159
- setTimeout(() => {
160
- server.close();
161
- reject(new Error("Login timeout - no callback received"));
162
- }, 5 * 60 * 1000);
163
- });
164
- });
165
- }
@@ -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
@@ -1,15 +0,0 @@
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"]
@@ -1,137 +0,0 @@
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)
@@ -1,3 +0,0 @@
1
- flask>=2.3.0
2
- requests>=2.31.0
3
- gunicorn>=21.0.0
@@ -1,19 +0,0 @@
1
- # OpenMemory using official npm package
2
- FROM node:20-alpine
3
-
4
- WORKDIR /app
5
-
6
- # Install openmemory-js from npm (waiting for release with MCP fix)
7
- RUN npm install openmemory-js@1.3.2
8
-
9
- # Create data directory
10
- RUN mkdir -p /data && chown -R node:node /data /app
11
-
12
- USER node
13
-
14
- EXPOSE 8080
15
-
16
- HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
17
- CMD node -e "require('http').get('http://localhost:8080/health', (res) => process.exit(res.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))"
18
-
19
- CMD ["npm", "start", "--prefix", "/app/node_modules/openmemory-js"]