vplex-memory 2.3.3 → 2.4.1

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.
package/README.md CHANGED
@@ -11,7 +11,7 @@ Works with Claude Code, Cursor, VS Code Copilot, Windsurf, Codex, and any MCP-co
11
11
  "mcpServers": {
12
12
  "vplex-memory": {
13
13
  "command": "npx",
14
- "args": ["-y", "vplex-memory@2.3.2"]
14
+ "args": ["-y", "vplex-memory@2.4.0"]
15
15
  }
16
16
  }
17
17
  }
@@ -30,7 +30,7 @@ Add to `.mcp.json` in your project root or `~/.claude/settings.json` globally:
30
30
  "mcpServers": {
31
31
  "vplex-memory": {
32
32
  "command": "npx",
33
- "args": ["-y", "vplex-memory@2.3.2"]
33
+ "args": ["-y", "vplex-memory@2.4.0"]
34
34
  }
35
35
  }
36
36
  }
@@ -46,7 +46,7 @@ Add to `.cursor/mcp.json`:
46
46
  "vplex-memory": {
47
47
  "type": "stdio",
48
48
  "command": "npx",
49
- "args": ["-y", "vplex-memory@2.3.2"]
49
+ "args": ["-y", "vplex-memory@2.4.0"]
50
50
  }
51
51
  }
52
52
  }
@@ -62,7 +62,7 @@ Add to `.vscode/mcp.json`:
62
62
  "vplex-memory": {
63
63
  "type": "stdio",
64
64
  "command": "npx",
65
- "args": ["-y", "vplex-memory@2.3.2"]
65
+ "args": ["-y", "vplex-memory@2.4.0"]
66
66
  }
67
67
  }
68
68
  }
@@ -78,7 +78,7 @@ Add to `~/.windsurf/mcp.json`:
78
78
  "vplex-memory": {
79
79
  "type": "stdio",
80
80
  "command": "npx",
81
- "args": ["-y", "vplex-memory@2.3.2"]
81
+ "args": ["-y", "vplex-memory@2.4.0"]
82
82
  }
83
83
  }
84
84
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vplex-memory",
3
- "version": "2.3.3",
3
+ "version": "2.4.1",
4
4
  "description": "VPLEX Memory MCP Server — persistent cross-session memory for AI coding tools",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,9 +17,9 @@
17
17
  * v2.1 — Security hardened
18
18
  */
19
19
 
20
- import { readFileSync, writeFileSync, statSync, mkdirSync, realpathSync } from "fs";
20
+ import { readFileSync, writeFileSync, statSync, mkdirSync, realpathSync, unlinkSync } from "fs";
21
21
  import { homedir, platform } from "os";
22
- import { join, resolve, relative } from "path";
22
+ import { join, resolve, relative, dirname, parse } from "path";
23
23
  import { createInterface } from "readline";
24
24
 
25
25
  const API_URL = "https://termplex-api.vercel.app";
@@ -411,9 +411,34 @@ function hashString(str) {
411
411
  return Math.abs(hash).toString(36);
412
412
  }
413
413
 
414
- const projectHash = hashString(process.cwd());
415
- const projectName = process.cwd().replace(/\\/g, "/").split("/").pop() || process.cwd();
416
- const projectPath = process.cwd();
414
+ // Detect project root: env override > .git traversal > cwd fallback
415
+ function detectProjectRoot() {
416
+ // Explicit override via env (set in MCP config)
417
+ const envPath = process.env.VPLEX_PROJECT_PATH || process.env.PROJECT_CWD;
418
+ if (envPath) {
419
+ try { statSync(envPath); return envPath; } catch { /* fall through */ }
420
+ }
421
+
422
+ // Walk up from cwd looking for .git (standard project root marker)
423
+ let dir = process.cwd();
424
+ const root = parse(dir).root;
425
+ while (dir !== root) {
426
+ try {
427
+ statSync(join(dir, ".git"));
428
+ return dir; // Found .git — this is the project root
429
+ } catch { /* continue */ }
430
+ const parent = dirname(dir);
431
+ if (parent === dir) break;
432
+ dir = parent;
433
+ }
434
+
435
+ return process.cwd();
436
+ }
437
+
438
+ const detectedRoot = detectProjectRoot();
439
+ const projectHash = hashString(detectedRoot);
440
+ const projectName = detectedRoot.replace(/\\/g, "/").split("/").pop() || detectedRoot;
441
+ const projectPath = detectedRoot;
417
442
 
418
443
  // ── HTTP Helper ─────────────────────────────────────────────────────
419
444
 
@@ -652,6 +677,16 @@ const TOOLS = [
652
677
  required: ["file_path"],
653
678
  },
654
679
  },
680
+ {
681
+ name: "memory_auth_status",
682
+ description: "Check authentication status: whether logged in, token expiry, and user info.",
683
+ inputSchema: { type: "object", properties: {} },
684
+ },
685
+ {
686
+ name: "memory_logout",
687
+ description: "Log out by deleting the cached session. Next tool call will require re-authentication.",
688
+ inputSchema: { type: "object", properties: {} },
689
+ },
655
690
  ];
656
691
 
657
692
  // ── Plan Verification (cached) ──────────────────────────────────────
@@ -1112,6 +1147,42 @@ async function handleToolCall(name, args) {
1112
1147
  };
1113
1148
  }
1114
1149
 
1150
+ case "memory_auth_status": {
1151
+ const session = readSession();
1152
+ if (!session?.token) {
1153
+ return { content: [{ type: "text", text: "Not authenticated. Call any memory tool to start the login flow." }] };
1154
+ }
1155
+ const exp = getTokenExpiry(session.token);
1156
+ const now = Math.floor(Date.now() / 1000);
1157
+ const remaining = exp > 0 ? exp - now : 0;
1158
+ const expiresIn = remaining > 0
1159
+ ? `${Math.floor(remaining / 3600)}h ${Math.floor((remaining % 3600) / 60)}m`
1160
+ : "expired";
1161
+ const user = session.user?.email || session.user?.name || "unknown";
1162
+ const hasRefresh = !!session.refresh_token;
1163
+ return {
1164
+ content: [{
1165
+ type: "text",
1166
+ text: `Authenticated as: ${user}\nToken expires in: ${expiresIn}${hasRefresh ? " (auto-refresh enabled)" : ""}\nSession file: ${getSessionPath()}`,
1167
+ }],
1168
+ };
1169
+ }
1170
+
1171
+ case "memory_logout": {
1172
+ const sessionPath = getSessionPath();
1173
+ try {
1174
+ unlinkSync(sessionPath);
1175
+ cachedToken = null;
1176
+ tokenReadAt = 0;
1177
+ return { content: [{ type: "text", text: `Logged out. Session deleted: ${sessionPath}\nNext memory tool call will require re-authentication.` }] };
1178
+ } catch (e) {
1179
+ if (e.code === "ENOENT") {
1180
+ return { content: [{ type: "text", text: "Already logged out (no session file found)." }] };
1181
+ }
1182
+ return { content: [{ type: "text", text: `Logout failed: ${e.message}` }], isError: true };
1183
+ }
1184
+ }
1185
+
1115
1186
  default:
1116
1187
  return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
1117
1188
  }