@cybermem/cli 0.6.11 → 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.
@@ -16,10 +16,10 @@ async function backup(options) {
16
16
  try {
17
17
  // Check if container exists
18
18
  try {
19
- await (0, execa_1.default)('docker', ['inspect', 'cybermem-openmemory']);
19
+ await (0, execa_1.default)('docker', ['inspect', 'cybermem-mcp']);
20
20
  }
21
21
  catch (e) {
22
- console.error(chalk_1.default.red('Error: cybermem-openmemory container not found. Is CyberMem installed?'));
22
+ console.error(chalk_1.default.red('Error: cybermem-mcp container not found. Is CyberMem installed?'));
23
23
  process.exit(1);
24
24
  }
25
25
  // Use a transient alpine container to tar the /data volume
@@ -27,7 +27,7 @@ async function backup(options) {
27
27
  // And we use --volumes-from to access the data volume of the running service
28
28
  const cmd = [
29
29
  'run', '--rm',
30
- '--volumes-from', 'cybermem-openmemory',
30
+ '--volumes-from', 'cybermem-mcp',
31
31
  '-v', `${process.cwd()}:/backup`,
32
32
  'alpine',
33
33
  'tar', 'czf', `/backup/${filename}`, '-C', '/', 'data'
@@ -0,0 +1,90 @@
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.dashboard = dashboard;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const net_1 = __importDefault(require("net"));
10
+ const open_1 = __importDefault(require("open"));
11
+ const os_1 = __importDefault(require("os"));
12
+ const path_1 = __importDefault(require("path"));
13
+ const TOKEN_FILE = path_1.default.join(os_1.default.homedir(), ".cybermem", "token.json");
14
+ const checkPort = (port) => {
15
+ return new Promise((resolve) => {
16
+ const socket = new net_1.default.Socket();
17
+ const onError = () => {
18
+ socket.destroy();
19
+ resolve(false);
20
+ };
21
+ socket.setTimeout(500);
22
+ socket.once("error", onError);
23
+ socket.once("timeout", onError);
24
+ socket.connect(port, "localhost", () => {
25
+ socket.end();
26
+ resolve(true);
27
+ });
28
+ });
29
+ };
30
+ /**
31
+ * Get stored token from ~/.cybermem/token.json
32
+ */
33
+ function getStoredToken() {
34
+ try {
35
+ if (!fs_1.default.existsSync(TOKEN_FILE))
36
+ return null;
37
+ const data = JSON.parse(fs_1.default.readFileSync(TOKEN_FILE, "utf-8"));
38
+ if (new Date(data.expires_at) < new Date()) {
39
+ console.warn(chalk_1.default.yellow("Token expired. Run: cybermem-cli login"));
40
+ return null;
41
+ }
42
+ return data.access_token;
43
+ }
44
+ catch {
45
+ return null;
46
+ }
47
+ }
48
+ async function dashboard(options) {
49
+ console.log(chalk_1.default.blue("Checking CyberMem stack status..."));
50
+ const [dashboardUp, prometheusUp] = await Promise.all([
51
+ checkPort(3000),
52
+ checkPort(9092),
53
+ ]);
54
+ if (!dashboardUp) {
55
+ console.error(chalk_1.default.red("❌ Dashboard is NOT running on port 3000."));
56
+ console.log(chalk_1.default.yellow("Run 'cybermem up' or 'cd packages/dashboard && npm run dev' to start it."));
57
+ }
58
+ else {
59
+ console.log(chalk_1.default.green("✅ Dashboard is running on port 3000."));
60
+ }
61
+ if (!prometheusUp) {
62
+ console.warn(chalk_1.default.yellow("⚠️ Prometheus is NOT running on port 9092."));
63
+ console.warn(chalk_1.default.gray(" Charts will be empty. Run 'cybermem up' or 'docker-compose up' to enable metrics."));
64
+ }
65
+ else {
66
+ console.log(chalk_1.default.green("✅ Prometheus is running on port 9092."));
67
+ }
68
+ if (dashboardUp) {
69
+ console.log(chalk_1.default.blue("\nOpening dashboard..."));
70
+ await (0, open_1.default)("http://localhost:3000");
71
+ }
72
+ else {
73
+ // Try remote dashboard if local isn't up
74
+ const token = getStoredToken();
75
+ if (token) {
76
+ // Check for remote URL from environment or config
77
+ const remoteUrl = process.env.CYBERMEM_DASHBOARD_URL;
78
+ if (remoteUrl) {
79
+ console.log(chalk_1.default.blue("\nOpening remote dashboard..."));
80
+ await (0, open_1.default)(`${remoteUrl}/api/auth/token?token=${token}`);
81
+ }
82
+ else {
83
+ console.log(chalk_1.default.gray("\nTip: Set CYBERMEM_DASHBOARD_URL to open remote dashboard."));
84
+ }
85
+ }
86
+ else {
87
+ console.log(chalk_1.default.gray("\nTip: Run 'cybermem-cli login' to enable remote access."));
88
+ }
89
+ }
90
+ }
@@ -3,63 +3,247 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.initCommand = void 0;
6
+ exports.init = init;
7
7
  const chalk_1 = __importDefault(require("chalk"));
8
- const commander_1 = require("commander");
8
+ const crypto_1 = __importDefault(require("crypto"));
9
+ const execa_1 = __importDefault(require("execa"));
9
10
  const fs_1 = __importDefault(require("fs"));
10
11
  const inquirer_1 = __importDefault(require("inquirer"));
12
+ const os_1 = __importDefault(require("os"));
11
13
  const path_1 = __importDefault(require("path"));
12
- exports.initCommand = new commander_1.Command('init')
13
- .description('Initialize CyberMem configuration')
14
- .action(async () => {
15
- console.log(chalk_1.default.bold.blue('🤖 CyberMem Setup Wizard'));
16
- const { target } = await inquirer_1.default.prompt([
17
- {
18
- type: 'list',
19
- name: 'target',
20
- message: 'Where do you want to deploy?',
21
- choices: [
22
- { name: 'Local (Docker Compose)', value: 'local' },
23
- { name: 'Raspberry Pi (Ansible)', value: 'rpi' },
24
- { name: 'VPS (Kubernetes/Helm)', value: 'vps' }
25
- ]
14
+ async function init(options) {
15
+ // Determine target from flags
16
+ let target = "local";
17
+ if (options.rpi)
18
+ target = "rpi";
19
+ if (options.vps)
20
+ target = "vps";
21
+ const useTailscale = options.remoteAccess;
22
+ console.log(chalk_1.default.blue(`Initializing CyberMem (${target})...`));
23
+ try {
24
+ // Resolve Template Directory (Support both Dev and Prod)
25
+ let templateDir = path_1.default.resolve(__dirname, "../../templates");
26
+ if (!fs_1.default.existsSync(templateDir)) {
27
+ templateDir = path_1.default.resolve(__dirname, "../../../templates");
26
28
  }
27
- ]);
28
- console.log(chalk_1.default.green(`Selected target: ${target}`));
29
- if (target === 'local') {
30
- const { confirm } = await inquirer_1.default.prompt([
31
- {
32
- type: 'confirm',
33
- name: 'confirm',
34
- message: 'This will create docker-compose.yml and .env in current directory. Continue?',
35
- default: true
29
+ if (!fs_1.default.existsSync(templateDir)) {
30
+ templateDir = path_1.default.resolve(process.cwd(), "packages/cli/templates");
31
+ }
32
+ if (!fs_1.default.existsSync(templateDir)) {
33
+ // Fallback for different build structures
34
+ templateDir = path_1.default.resolve(__dirname, "../templates");
35
+ }
36
+ if (!fs_1.default.existsSync(templateDir)) {
37
+ throw new Error(`Templates not found at ${templateDir}. Please ensure package is built correctly.`);
38
+ }
39
+ if (target === "local") {
40
+ const composeFile = path_1.default.join(templateDir, "docker-compose.yml");
41
+ if (!fs_1.default.existsSync(composeFile)) {
42
+ console.error(chalk_1.default.red(`Internal Error: Template not found at ${composeFile}`));
43
+ process.exit(1);
44
+ }
45
+ // Home Directory Config
46
+ const homeDir = os_1.default.homedir();
47
+ const configDir = path_1.default.join(homeDir, ".cybermem");
48
+ const envFile = path_1.default.join(configDir, ".env");
49
+ const dataDir = path_1.default.join(configDir, "data");
50
+ // 1. Ensure ~/.cybermem exists
51
+ if (!fs_1.default.existsSync(configDir)) {
52
+ fs_1.default.mkdirSync(configDir, { recursive: true });
53
+ fs_1.default.mkdirSync(dataDir, { recursive: true });
54
+ }
55
+ // 2. Local Mode
56
+ if (!fs_1.default.existsSync(envFile)) {
57
+ console.log(chalk_1.default.yellow(`Initializing local configuration in ${configDir}...`));
58
+ const templateEnv = path_1.default.join(templateDir, "envs/local.env");
59
+ const envContent = fs_1.default.readFileSync(templateEnv, "utf-8");
60
+ fs_1.default.writeFileSync(envFile, envContent);
61
+ console.log(chalk_1.default.green(`Created .env at ${envFile}`));
62
+ }
63
+ console.log(chalk_1.default.blue("Starting CyberMem services in Local Mode..."));
64
+ await (0, execa_1.default)("docker-compose", [
65
+ "-f",
66
+ composeFile,
67
+ "--env-file",
68
+ envFile,
69
+ "--project-name",
70
+ "cybermem",
71
+ "up",
72
+ "-d",
73
+ "--remove-orphans",
74
+ ], {
75
+ stdio: "inherit",
76
+ env: {
77
+ ...process.env,
78
+ DATA_DIR: dataDir,
79
+ CYBERMEM_ENV_PATH: envFile,
80
+ OM_API_KEY: "",
81
+ },
82
+ });
83
+ console.log(chalk_1.default.green("\n🎉 CyberMem Installed!"));
84
+ console.log("");
85
+ console.log(chalk_1.default.bold("Next Steps:"));
86
+ console.log(` 1. Open ${chalk_1.default.underline("http://localhost:3000/client-connect")} to connect your MCP clients`);
87
+ console.log(` 2. Default password: ${chalk_1.default.bold("admin")} (you'll be prompted to change it)`);
88
+ console.log("");
89
+ console.log(chalk_1.default.dim("Local mode is active: No API key required for connections from this laptop."));
90
+ }
91
+ else if (target === "rpi" || target === "vps") {
92
+ const composeFile = path_1.default.join(templateDir, "docker-compose.yml");
93
+ const answers = await inquirer_1.default.prompt([
94
+ {
95
+ type: "input",
96
+ name: "host",
97
+ message: "Enter SSH Host (e.g. pi@raspberrypi.local):",
98
+ validate: (input) => input.includes("@") ? true : "Format must be user@host",
99
+ },
100
+ ]);
101
+ const sshHost = answers.host;
102
+ console.log(chalk_1.default.blue(`Remote deploying to ${sshHost}...`));
103
+ // 1. Create remote directory
104
+ await (0, execa_1.default)("ssh", [sshHost, "mkdir -p ~/.cybermem/data"]);
105
+ // 1.5 Check and fix Docker architecture (64-bit kernel with 32-bit Docker)
106
+ console.log(chalk_1.default.blue("Checking Docker architecture..."));
107
+ try {
108
+ const { stdout: kernelArch } = await (0, execa_1.default)("ssh", [
109
+ sshHost,
110
+ "uname -m",
111
+ ]);
112
+ const { stdout: dockerArch } = await (0, execa_1.default)("ssh", [
113
+ sshHost,
114
+ 'docker version --format "{{.Server.Arch}}" 2>/dev/null || echo "unknown"',
115
+ ]);
116
+ if (kernelArch.trim() === "aarch64" && dockerArch.trim() !== "arm64") {
117
+ console.log(chalk_1.default.yellow(`⚠️ Docker is ${dockerArch.trim()}, kernel is aarch64. Installing arm64 Docker...`));
118
+ const installCmd = `
119
+ sudo systemctl stop docker docker.socket 2>/dev/null || true
120
+ curl -fsSL https://download.docker.com/linux/static/stable/aarch64/docker-27.5.1.tgz -o /tmp/docker.tgz
121
+ sudo tar -xzf /tmp/docker.tgz -C /usr/local/bin --strip-components=1
122
+ sudo /usr/local/bin/dockerd &
123
+ sleep 5
124
+ docker version --format "{{.Server.Arch}}"
125
+ `;
126
+ const { stdout } = await (0, execa_1.default)("ssh", [sshHost, installCmd], {
127
+ shell: true,
128
+ });
129
+ if (stdout.includes("arm64")) {
130
+ console.log(chalk_1.default.green("✅ Docker arm64 installed successfully"));
131
+ }
132
+ else {
133
+ console.log(chalk_1.default.yellow("⚠️ Docker arm64 install may need manual verification"));
134
+ }
135
+ }
136
+ else if (dockerArch.trim() === "arm64") {
137
+ console.log(chalk_1.default.green(`✅ Docker is already arm64`));
138
+ }
139
+ else {
140
+ console.log(chalk_1.default.gray(`Docker arch: ${dockerArch.trim()}, kernel: ${kernelArch.trim()}`));
141
+ }
142
+ }
143
+ catch (e) {
144
+ console.log(chalk_1.default.yellow(`⚠️ Docker arch check skipped: ${e.message}`));
145
+ }
146
+ // 2. Initial Env Setup (if missing)
147
+ try {
148
+ await (0, execa_1.default)("ssh", [sshHost, "[ -f ~/.cybermem/.env ]"]);
149
+ console.log(chalk_1.default.gray("Remote .env exists, skipping generation."));
36
150
  }
37
- ]);
38
- if (confirm) {
39
- const templateDir = path_1.default.resolve(__dirname, '../../templates');
40
- const envSrc = path_1.default.join(templateDir, 'envs/local.example');
41
- if (!fs_1.default.existsSync(envSrc)) {
42
- console.error(chalk_1.default.red(`Template not found at ${envSrc}.`));
43
- return;
151
+ catch (e) {
152
+ console.log(chalk_1.default.yellow("Generating remote configuration..."));
153
+ let templateName = "rpi.env";
154
+ if (target === "vps")
155
+ templateName = "vps.env";
156
+ else if (useTailscale)
157
+ templateName = "rpi-tailscale.env";
158
+ const templateEnv = path_1.default.join(templateDir, "envs", templateName);
159
+ let envContent = fs_1.default.readFileSync(templateEnv, "utf-8");
160
+ const newKey = `cm-${crypto_1.default.randomBytes(16).toString("hex")}`;
161
+ // Replace OM_API_KEY with generated key
162
+ if (envContent.includes("OM_API_KEY=")) {
163
+ envContent = envContent.replace(/OM_API_KEY=.*/, `OM_API_KEY=${newKey}`);
164
+ }
165
+ const tempEnv = path_1.default.join(os_1.default.tmpdir(), "cybermem-remote.env");
166
+ fs_1.default.writeFileSync(tempEnv, envContent);
167
+ await (0, execa_1.default)("scp", [tempEnv, `${sshHost}:~/.cybermem/.env`]);
168
+ fs_1.default.unlinkSync(tempEnv);
169
+ console.log(chalk_1.default.green(`✅ Security configuration generated (${templateName}).`));
44
170
  }
45
- // Generate .env
46
- let envContent = fs_1.default.readFileSync(envSrc, 'utf-8');
47
- const crypto = require('crypto');
48
- const newKey = `sk-${crypto.randomBytes(16).toString('hex')}`;
49
- // Replace placeholder or key
50
- if (envContent.includes('key-change-me') || envContent.includes('OM_API_KEY=')) {
51
- envContent = envContent.replace(/OM_API_KEY=.*/, `OM_API_KEY=${newKey}`);
171
+ // 3. Copy Docker Compose
172
+ console.log(chalk_1.default.blue("Uploading templates..."));
173
+ await (0, execa_1.default)("scp", [
174
+ composeFile,
175
+ `${sshHost}:~/.cybermem/docker-compose.yml`,
176
+ ]);
177
+ // 4. Run Docker Compose Remotely
178
+ console.log(chalk_1.default.blue("Starting services on RPi..."));
179
+ // DOCKER_DEFAULT_PLATFORM=linux/arm64 forces arm64 images on RPi with 64-bit kernel but 32-bit Docker
180
+ const remoteCmd = `
181
+ export CYBERMEM_ENV_PATH=~/.cybermem/.env
182
+ export DATA_DIR=~/.cybermem/data
183
+ export DOCKER_DEFAULT_PLATFORM=linux/arm64
184
+ docker-compose -f ~/.cybermem/docker-compose.yml up -d --remove-orphans
185
+ `;
186
+ await (0, execa_1.default)("ssh", [sshHost, remoteCmd], { stdio: "inherit" });
187
+ console.log(chalk_1.default.green("\n✅ RPi deployment successful!"));
188
+ const hostIp = sshHost.split("@")[1];
189
+ console.log(chalk_1.default.bold("Access Points (LAN):"));
190
+ console.log(` - Dashboard: ${chalk_1.default.underline(`http://${hostIp}:3000`)} (admin/admin)`);
191
+ console.log(` - OpenMemory: ${chalk_1.default.underline(`http://${hostIp}:8080`)}`);
192
+ // Tailscale Funnel setup
193
+ if (useTailscale) {
194
+ console.log(chalk_1.default.blue("\n🔗 Setting up Remote Access (Tailscale Funnel)..."));
195
+ try {
196
+ try {
197
+ await (0, execa_1.default)("ssh", [sshHost, "which tailscale"]);
198
+ }
199
+ catch (e) {
200
+ console.log(chalk_1.default.yellow(" Tailscale not found. Installing..."));
201
+ await (0, execa_1.default)("ssh", [sshHost, "curl -fsSL https://tailscale.com/install.sh | sh"], { stdio: "inherit" });
202
+ }
203
+ console.log(chalk_1.default.blue(" Ensuring Tailscale is up..."));
204
+ try {
205
+ await (0, execa_1.default)("ssh", [sshHost, "tailscale status"]);
206
+ }
207
+ catch (e) {
208
+ console.log(chalk_1.default.yellow(" ⚠️ Tailscale authentication required. Please follow the prompts:"));
209
+ await (0, execa_1.default)("ssh", [sshHost, "sudo tailscale up"], {
210
+ stdio: "inherit",
211
+ });
212
+ }
213
+ console.log(chalk_1.default.blue(" Configuring HTTPS Funnel (requires sudo access)..."));
214
+ console.log(chalk_1.default.gray(" You may be prompted for your RPi password."));
215
+ await (0, execa_1.default)("ssh", ["-t", sshHost, "sudo tailscale serve reset"], {
216
+ stdio: "inherit",
217
+ }).catch(() => { });
218
+ await (0, execa_1.default)("ssh", [
219
+ "-t",
220
+ sshHost,
221
+ "sudo tailscale serve --bg --set-path /cybermem http://127.0.0.1:8626",
222
+ ], { stdio: "inherit" });
223
+ await (0, execa_1.default)("ssh", ["-t", sshHost, "sudo tailscale serve --bg http://127.0.0.1:3000"], { stdio: "inherit" });
224
+ await (0, execa_1.default)("ssh", ["-t", sshHost, "sudo tailscale funnel --bg 443"], { stdio: "inherit" });
225
+ const { stdout } = await (0, execa_1.default)("ssh", [
226
+ sshHost,
227
+ "tailscale status --json | jq -r '.Self.DNSName' | sed 's/\\.$//'",
228
+ ]);
229
+ const dnsName = stdout.trim();
230
+ console.log(chalk_1.default.green("\n🌐 Remote Access Active (HTTPS):"));
231
+ console.log(` - Dashboard: ${chalk_1.default.underline(`https://${dnsName}/`)}`);
232
+ console.log(` - MCP API: ${chalk_1.default.underline(`https://${dnsName}/cybermem/mcp`)}`);
233
+ }
234
+ catch (e) {
235
+ console.log(chalk_1.default.red("\n❌ Remote Access setup failed:"));
236
+ console.error(e);
237
+ console.log(chalk_1.default.gray("Manual setup: curl -fsSL https://tailscale.com/install.sh | sh && sudo tailscale up"));
238
+ }
52
239
  }
53
240
  else {
54
- envContent += `\nOM_API_KEY=${newKey}\n`;
241
+ console.log(chalk_1.default.gray("\n💡 For remote access, re-run with: npx @cybermem/cli --rpi --remote-access"));
55
242
  }
56
- fs_1.default.writeFileSync('.env', envContent);
57
- console.log(chalk_1.default.gray('Created .env with generated API Key'));
58
- console.log(chalk_1.default.gray('(Docker Compose configuration is managed internally by the CLI)'));
59
- console.log(chalk_1.default.green('\nInitialization complete! Run "cybermem deploy" to start.'));
60
243
  }
61
244
  }
62
- else {
63
- console.log(chalk_1.default.yellow('Target init not fully implemented in CLI yet.'));
245
+ catch (error) {
246
+ console.error(chalk_1.default.red("Deployment failed:"), error);
247
+ process.exit(1);
64
248
  }
65
- });
249
+ }
@@ -0,0 +1,165 @@
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
+ }
@@ -10,7 +10,7 @@ const ora_1 = __importDefault(require("ora"));
10
10
  async function reset() {
11
11
  const spinner = (0, ora_1.default)('Resetting CyberMem database...').start();
12
12
  try {
13
- const containerName = 'cybermem-openmemory';
13
+ const containerName = 'cybermem-mcp';
14
14
  // Check if container exists
15
15
  try {
16
16
  (0, child_process_1.execSync)(`docker inspect ${containerName}`, { stdio: 'pipe' });
@@ -25,7 +25,7 @@ async function restore(file, options) {
25
25
  // 1. Stop the OpenMemory service to safely write to DB
26
26
  console.log(chalk_1.default.blue('Stopping OpenMemory service...'));
27
27
  try {
28
- await (0, execa_1.default)('docker', ['stop', 'cybermem-openmemory']);
28
+ await (0, execa_1.default)('docker', ['stop', 'cybermem-mcp']);
29
29
  }
30
30
  catch (e) {
31
31
  console.log(chalk_1.default.gray('Container not running (or not found), proceeding...'));
@@ -37,7 +37,7 @@ async function restore(file, options) {
37
37
  const filename = path_1.default.basename(backupPath);
38
38
  const cmd = [
39
39
  'run', '--rm',
40
- '--volumes-from', 'cybermem-openmemory', // Access the volume even if container is stopped
40
+ '--volumes-from', 'cybermem-mcp', // Access the volume even if container is stopped
41
41
  '-v', `${dir}:/backup`,
42
42
  'alpine',
43
43
  'sh', '-c',
@@ -50,13 +50,13 @@ async function restore(file, options) {
50
50
  await (0, execa_1.default)('docker', cmd, { stdio: 'inherit' });
51
51
  // 3. Restart the service
52
52
  console.log(chalk_1.default.blue('Restarting OpenMemory service...'));
53
- await (0, execa_1.default)('docker', ['start', 'cybermem-openmemory']);
53
+ await (0, execa_1.default)('docker', ['start', 'cybermem-mcp']);
54
54
  console.log(chalk_1.default.green(`\n✅ Restore completed successfully!`));
55
55
  console.log('Your memory has been recovered.');
56
56
  }
57
57
  catch (error) {
58
58
  console.error(chalk_1.default.red('Restore failed:'), error);
59
- console.log(chalk_1.default.yellow('Suggestion: Check if Docker is running and "cybermem-openmemory" container exists.'));
59
+ console.log(chalk_1.default.yellow('Suggestion: Check if Docker is running and "cybermem-mcp" container exists.'));
60
60
  process.exit(1);
61
61
  }
62
62
  }