@shodh/memory-mcp 0.1.80 → 0.1.90
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 +68 -11
- package/dist/index.js +247 -47
- package/package.json +8 -1
- package/scripts/postinstall.cjs +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<h1 align="center">Shodh-Memory MCP Server</h1>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<strong>v0.1.
|
|
8
|
+
<strong>v0.1.90</strong> | Persistent cognitive memory for AI agents
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
@@ -29,9 +29,11 @@
|
|
|
29
29
|
- **Semantic Search**: Find memories by meaning using MiniLM-L6 embeddings
|
|
30
30
|
- **Knowledge Graph**: Entity extraction and relationship tracking
|
|
31
31
|
- **Memory Consolidation**: Automatic decay, replay, and strengthening
|
|
32
|
+
- **Idempotent**: Content-hash dedup — identical memories are never stored twice
|
|
32
33
|
- **1-Click Install**: Auto-downloads native server binary for your platform
|
|
33
34
|
- **Offline-First**: All models auto-downloaded on first run (~38MB total), no internet required after
|
|
34
|
-
- **Fast**:
|
|
35
|
+
- **Fast**: <200ms API response, sub-millisecond graph lookup, 30-50ms semantic search
|
|
36
|
+
- **GTD Task Management**: Full todo system with projects, subtasks, comments, and reminders
|
|
35
37
|
|
|
36
38
|
## Installation
|
|
37
39
|
|
|
@@ -81,25 +83,80 @@ env = { SHODH_API_KEY = "your-api-key-here" }
|
|
|
81
83
|
| `SHODH_STREAM` | Enable/disable streaming ingestion | `true` |
|
|
82
84
|
| `SHODH_PROACTIVE` | Enable/disable proactive memory surfacing | `true` |
|
|
83
85
|
|
|
84
|
-
## MCP Tools (
|
|
86
|
+
## MCP Tools (47 total)
|
|
87
|
+
|
|
88
|
+
<details>
|
|
89
|
+
<summary><b>Memory</b> — Store, search, and manage memories</summary>
|
|
85
90
|
|
|
86
91
|
| Tool | Description |
|
|
87
92
|
|------|-------------|
|
|
88
|
-
| `remember` | Store a memory with optional type and
|
|
93
|
+
| `remember` | Store a memory with optional type, tags, and metadata |
|
|
89
94
|
| `recall` | Semantic search to find relevant memories |
|
|
90
95
|
| `proactive_context` | Auto-surface relevant memories for current context |
|
|
91
96
|
| `context_summary` | Get categorized context for session bootstrap |
|
|
92
97
|
| `list_memories` | List all stored memories |
|
|
98
|
+
| `read_memory` | Read full content of a specific memory by ID |
|
|
93
99
|
| `forget` | Delete a specific memory by ID |
|
|
94
|
-
| `
|
|
95
|
-
|
|
100
|
+
| `reinforce` | Reinforce a memory (boost importance) |
|
|
101
|
+
</details>
|
|
102
|
+
|
|
103
|
+
<details>
|
|
104
|
+
<summary><b>Todos (GTD)</b> — Task management with projects and subtasks</summary>
|
|
105
|
+
|
|
106
|
+
| Tool | Description |
|
|
107
|
+
|------|-------------|
|
|
108
|
+
| `add_todo` | Create a task with priority, due date, project, contexts |
|
|
109
|
+
| `list_todos` | List/search todos with semantic or GTD-style filtering |
|
|
110
|
+
| `update_todo` | Update task properties (status, priority, notes) |
|
|
111
|
+
| `complete_todo` | Mark a task as done (auto-creates next for recurring) |
|
|
112
|
+
| `delete_todo` | Permanently delete a task |
|
|
113
|
+
| `reorder_todo` | Move a task up or down within its status group |
|
|
114
|
+
| `list_subtasks` | List subtasks of a parent todo |
|
|
115
|
+
| `add_todo_comment` | Add a comment to a task (progress, resolution) |
|
|
116
|
+
| `list_todo_comments` | List all comments on a task |
|
|
117
|
+
| `update_todo_comment` | Edit an existing comment |
|
|
118
|
+
| `delete_todo_comment` | Delete a comment |
|
|
119
|
+
| `todo_stats` | Get todo statistics by status, overdue items |
|
|
120
|
+
</details>
|
|
121
|
+
|
|
122
|
+
<details>
|
|
123
|
+
<summary><b>Projects</b> — Organize todos into groups</summary>
|
|
124
|
+
|
|
125
|
+
| Tool | Description |
|
|
126
|
+
|------|-------------|
|
|
127
|
+
| `add_project` | Create a project with optional parent (sub-projects) |
|
|
128
|
+
| `list_projects` | List all projects with todo counts |
|
|
129
|
+
| `archive_project` | Archive a project (hidden but restorable) |
|
|
130
|
+
| `delete_project` | Permanently delete a project |
|
|
131
|
+
</details>
|
|
132
|
+
|
|
133
|
+
<details>
|
|
134
|
+
<summary><b>Reminders</b> — Time, duration, and context-triggered reminders</summary>
|
|
135
|
+
|
|
136
|
+
| Tool | Description |
|
|
137
|
+
|------|-------------|
|
|
138
|
+
| `set_reminder` | Set a reminder (time, duration, or keyword trigger) |
|
|
139
|
+
| `list_reminders` | List pending/triggered/dismissed reminders |
|
|
140
|
+
| `dismiss_reminder` | Acknowledge a triggered reminder |
|
|
141
|
+
</details>
|
|
142
|
+
|
|
143
|
+
<details>
|
|
144
|
+
<summary><b>System</b> — Health, backups, and diagnostics</summary>
|
|
145
|
+
|
|
146
|
+
| Tool | Description |
|
|
147
|
+
|------|-------------|
|
|
96
148
|
| `memory_stats` | Get statistics about stored memories |
|
|
97
|
-
| `
|
|
98
|
-
| `
|
|
99
|
-
| `
|
|
100
|
-
| `
|
|
149
|
+
| `verify_index` | Check vector index integrity |
|
|
150
|
+
| `repair_index` | Re-index orphaned memories |
|
|
151
|
+
| `token_status` | Get current session token usage |
|
|
152
|
+
| `reset_token_session` | Reset token counter for new session |
|
|
101
153
|
| `consolidation_report` | View memory consolidation activity |
|
|
102
|
-
| `
|
|
154
|
+
| `backup_create` | Create a backup of all memories |
|
|
155
|
+
| `backup_list` | List available backups |
|
|
156
|
+
| `backup_verify` | Verify backup integrity (SHA-256) |
|
|
157
|
+
| `backup_restore` | Restore from a backup |
|
|
158
|
+
| `backup_purge` | Purge old backups, keep most recent N |
|
|
159
|
+
</details>
|
|
103
160
|
|
|
104
161
|
## REST API (for Developers)
|
|
105
162
|
|
package/dist/index.js
CHANGED
|
@@ -4881,28 +4881,74 @@ class StdioServerTransport {
|
|
|
4881
4881
|
import { spawn } from "child_process";
|
|
4882
4882
|
import * as path from "path";
|
|
4883
4883
|
import * as fs from "fs";
|
|
4884
|
+
import * as crypto from "crypto";
|
|
4884
4885
|
import { fileURLToPath } from "url";
|
|
4885
|
-
|
|
4886
|
-
|
|
4886
|
+
|
|
4887
|
+
// security-utils.ts
|
|
4888
|
+
function isLocalHostFromUrl(apiUrl) {
|
|
4889
|
+
try {
|
|
4890
|
+
const url = new URL(apiUrl);
|
|
4891
|
+
const host = url.hostname;
|
|
4892
|
+
return host === "127.0.0.1" || host === "localhost" || host === "::1" || host === "0.0.0.0";
|
|
4893
|
+
} catch {
|
|
4894
|
+
return false;
|
|
4895
|
+
}
|
|
4896
|
+
}
|
|
4897
|
+
function shouldWarnInsecureApiUrl(apiUrl, allowHttpEnv) {
|
|
4898
|
+
return !isLocalHostFromUrl(apiUrl) && apiUrl.startsWith("http://") && allowHttpEnv !== "true";
|
|
4899
|
+
}
|
|
4900
|
+
function serializeAndValidateBody(body, maxLength) {
|
|
4901
|
+
const serialized = JSON.stringify(body);
|
|
4902
|
+
if (serialized.length > maxLength) {
|
|
4903
|
+
return { ok: false, error: `Request body exceeds maximum length of ${maxLength} characters` };
|
|
4904
|
+
}
|
|
4905
|
+
return { ok: true, serialized };
|
|
4906
|
+
}
|
|
4907
|
+
function nextReconnectDelay(currentDelayMs, maxDelayMs) {
|
|
4908
|
+
const safeCurrent = Math.max(currentDelayMs, 1000);
|
|
4909
|
+
return Math.min(safeCurrent * 2, maxDelayMs);
|
|
4910
|
+
}
|
|
4911
|
+
|
|
4912
|
+
// index.ts
|
|
4913
|
+
var __filename2 = typeof import.meta !== "undefined" && import.meta.url ? fileURLToPath(import.meta.url) : "";
|
|
4914
|
+
var __dirname2 = __filename2 ? path.dirname(__filename2) : process.cwd();
|
|
4887
4915
|
var API_URL = process.env.SHODH_API_URL || "http://127.0.0.1:3030";
|
|
4888
4916
|
var WS_URL = API_URL.replace(/^http/, "ws") + "/api/stream";
|
|
4889
4917
|
var USER_ID = process.env.SHODH_USER_ID || "claude-code";
|
|
4890
|
-
|
|
4918
|
+
function isLocalServer() {
|
|
4919
|
+
try {
|
|
4920
|
+
const url = new URL(API_URL);
|
|
4921
|
+
const host = url.hostname;
|
|
4922
|
+
return host === "127.0.0.1" || host === "localhost" || host === "::1" || host === "0.0.0.0";
|
|
4923
|
+
} catch {
|
|
4924
|
+
return false;
|
|
4925
|
+
}
|
|
4926
|
+
}
|
|
4927
|
+
var SANDBOX_MODE = process.env.SMITHERY_SANDBOX === "true";
|
|
4928
|
+
var API_KEY = process.env.SHODH_API_KEY || (SANDBOX_MODE ? "sandbox" : "");
|
|
4891
4929
|
if (!API_KEY) {
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4930
|
+
if (isLocalServer()) {
|
|
4931
|
+
API_KEY = crypto.randomBytes(32).toString("hex");
|
|
4932
|
+
console.error("[shodh-memory] No API key set — auto-generated for local server.");
|
|
4933
|
+
} else {
|
|
4934
|
+
console.error("ERROR: SHODH_API_KEY is required for remote servers.");
|
|
4935
|
+
console.error("");
|
|
4936
|
+
console.error("To fix, add to your MCP config (claude_desktop_config.json or mcp.json):");
|
|
4937
|
+
console.error(` "env": { "SHODH_API_KEY": "your-api-key" }`);
|
|
4938
|
+
console.error("");
|
|
4939
|
+
console.error("Or set in your shell:");
|
|
4940
|
+
console.error(" export SHODH_API_KEY=your-api-key");
|
|
4941
|
+
process.exit(1);
|
|
4942
|
+
}
|
|
4902
4943
|
}
|
|
4903
4944
|
var RETRY_ATTEMPTS = 3;
|
|
4904
4945
|
var RETRY_DELAY_MS = 1000;
|
|
4905
4946
|
var REQUEST_TIMEOUT_MS = 1e4;
|
|
4947
|
+
var WRITE_TIMEOUT_MS = 30000;
|
|
4948
|
+
if (shouldWarnInsecureApiUrl(API_URL, process.env.SHODH_ALLOW_HTTP)) {
|
|
4949
|
+
console.error("[shodh-memory] WARNING: Using HTTP for a non-localhost server is insecure.");
|
|
4950
|
+
console.error("[shodh-memory] Set SHODH_API_URL to an https:// URL, or set SHODH_ALLOW_HTTP=true to suppress this warning.");
|
|
4951
|
+
}
|
|
4906
4952
|
var MAX_CONTENT_LENGTH = 1e5;
|
|
4907
4953
|
var MAX_QUERY_LENGTH = 1e4;
|
|
4908
4954
|
var MAX_LIMIT = 250;
|
|
@@ -4930,10 +4976,28 @@ var STREAM_ENABLED = process.env.SHODH_STREAM !== "false";
|
|
|
4930
4976
|
var STREAM_MIN_CONTENT_LENGTH = 50;
|
|
4931
4977
|
var PROACTIVE_SURFACING = process.env.SHODH_PROACTIVE !== "false";
|
|
4932
4978
|
var PROACTIVE_MIN_CONTEXT_LENGTH = 30;
|
|
4979
|
+
var MAX_CONTEXT_LENGTH = 4000;
|
|
4980
|
+
function stripSystemNoise(text) {
|
|
4981
|
+
let result = text;
|
|
4982
|
+
const tagPatterns = [
|
|
4983
|
+
/<task-notification>[\s\S]*?<\/task-notification>/g,
|
|
4984
|
+
/<system-reminder>[\s\S]*?<\/system-reminder>/g,
|
|
4985
|
+
/<shodh-context[\s\S]*?<\/shodh-context>/g,
|
|
4986
|
+
/<shodh-memory[\s\S]*?<\/shodh-memory>/g,
|
|
4987
|
+
/<command-name>[\s\S]*?<\/command-name>/g
|
|
4988
|
+
];
|
|
4989
|
+
for (const pattern of tagPatterns) {
|
|
4990
|
+
result = result.replace(pattern, "");
|
|
4991
|
+
}
|
|
4992
|
+
result = result.replace(/\s{3,}/g, " ").trim();
|
|
4993
|
+
return result;
|
|
4994
|
+
}
|
|
4933
4995
|
var lastProactiveResponse = "";
|
|
4934
4996
|
var streamSocket = null;
|
|
4935
4997
|
var streamConnecting = false;
|
|
4936
4998
|
var streamReconnectTimer = null;
|
|
4999
|
+
var streamReconnectDelay = 1000;
|
|
5000
|
+
var STREAM_RECONNECT_MAX_DELAY = 60000;
|
|
4937
5001
|
var streamBuffer = [];
|
|
4938
5002
|
var MAX_BUFFER_SIZE = 100;
|
|
4939
5003
|
var streamHandshakeComplete = false;
|
|
@@ -4944,9 +5008,15 @@ async function connectStream() {
|
|
|
4944
5008
|
streamConnecting = true;
|
|
4945
5009
|
streamHandshakeComplete = false;
|
|
4946
5010
|
try {
|
|
4947
|
-
|
|
5011
|
+
const wsUrlWithAuth = WS_URL + (WS_URL.includes("?") ? "&" : "?") + "api_key=" + encodeURIComponent(API_KEY);
|
|
5012
|
+
streamSocket = new WebSocket(wsUrlWithAuth, {
|
|
5013
|
+
headers: {
|
|
5014
|
+
"X-API-Key": API_KEY
|
|
5015
|
+
}
|
|
5016
|
+
});
|
|
4948
5017
|
streamSocket.onopen = () => {
|
|
4949
5018
|
streamConnecting = false;
|
|
5019
|
+
streamReconnectDelay = 1000;
|
|
4950
5020
|
console.error("[Stream] WebSocket connected to", WS_URL);
|
|
4951
5021
|
const handshake = JSON.stringify({
|
|
4952
5022
|
user_id: USER_ID,
|
|
@@ -4988,11 +5058,13 @@ async function connectStream() {
|
|
|
4988
5058
|
streamConnecting = false;
|
|
4989
5059
|
streamHandshakeComplete = false;
|
|
4990
5060
|
if (STREAM_ENABLED && !streamReconnectTimer) {
|
|
5061
|
+
const delay = streamReconnectDelay;
|
|
5062
|
+
streamReconnectDelay = nextReconnectDelay(streamReconnectDelay, STREAM_RECONNECT_MAX_DELAY);
|
|
4991
5063
|
streamReconnectTimer = setTimeout(() => {
|
|
4992
5064
|
streamReconnectTimer = null;
|
|
4993
|
-
console.error(
|
|
5065
|
+
console.error(`[Stream] Attempting reconnect (next delay: ${streamReconnectDelay}ms)...`);
|
|
4994
5066
|
connectStream().catch((e) => console.error("[Stream] Reconnect failed:", e));
|
|
4995
|
-
},
|
|
5067
|
+
}, delay);
|
|
4996
5068
|
}
|
|
4997
5069
|
};
|
|
4998
5070
|
streamSocket.onerror = (error) => {
|
|
@@ -5076,7 +5148,7 @@ async function surfaceRelevant(context, maxResults = 3) {
|
|
|
5076
5148
|
function formatSurfacedMemories(memories) {
|
|
5077
5149
|
if (!memories || memories.length === 0)
|
|
5078
5150
|
return "";
|
|
5079
|
-
const formatted = memories.map((m, i) => ` ${i + 1}. [${(m.relevance_score * 100).toFixed(0)}%] ${m.content.slice(0, 80)}...`).join(`
|
|
5151
|
+
const formatted = memories.map((m, i) => ` ${i + 1}. [${((m.relevance_score ?? 0) * 100).toFixed(0)}%] ${m.content.slice(0, 80)}...`).join(`
|
|
5080
5152
|
`);
|
|
5081
5153
|
return `
|
|
5082
5154
|
|
|
@@ -5097,7 +5169,8 @@ async function apiCall(endpoint, method = "GET", body) {
|
|
|
5097
5169
|
for (let attempt = 1;attempt <= RETRY_ATTEMPTS; attempt++) {
|
|
5098
5170
|
try {
|
|
5099
5171
|
const controller = new AbortController;
|
|
5100
|
-
const
|
|
5172
|
+
const timeout = method === "GET" ? REQUEST_TIMEOUT_MS : WRITE_TIMEOUT_MS;
|
|
5173
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
5101
5174
|
const options = {
|
|
5102
5175
|
method,
|
|
5103
5176
|
headers: {
|
|
@@ -5107,7 +5180,11 @@ async function apiCall(endpoint, method = "GET", body) {
|
|
|
5107
5180
|
signal: controller.signal
|
|
5108
5181
|
};
|
|
5109
5182
|
if (body) {
|
|
5110
|
-
|
|
5183
|
+
const bodyValidation = serializeAndValidateBody(body, MAX_CONTENT_LENGTH);
|
|
5184
|
+
if (!bodyValidation.ok) {
|
|
5185
|
+
throw new Error(bodyValidation.error);
|
|
5186
|
+
}
|
|
5187
|
+
options.body = bodyValidation.serialized;
|
|
5111
5188
|
}
|
|
5112
5189
|
const response = await fetch(`${API_URL}${endpoint}`, options);
|
|
5113
5190
|
clearTimeout(timeoutId);
|
|
@@ -5148,7 +5225,7 @@ async function isServerAvailable() {
|
|
|
5148
5225
|
}
|
|
5149
5226
|
var server = new Server({
|
|
5150
5227
|
name: "shodh-memory",
|
|
5151
|
-
version: "0.1.
|
|
5228
|
+
version: "0.1.90"
|
|
5152
5229
|
}, {
|
|
5153
5230
|
capabilities: {
|
|
5154
5231
|
tools: {},
|
|
@@ -5375,6 +5452,20 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
5375
5452
|
}
|
|
5376
5453
|
}
|
|
5377
5454
|
},
|
|
5455
|
+
{
|
|
5456
|
+
name: "backup_restore",
|
|
5457
|
+
description: "Restore a previously created backup by ID. This replaces all current data for the user with the backup contents. Server restart is recommended after restore.",
|
|
5458
|
+
inputSchema: {
|
|
5459
|
+
type: "object",
|
|
5460
|
+
properties: {
|
|
5461
|
+
backup_id: {
|
|
5462
|
+
type: "number",
|
|
5463
|
+
description: "The backup ID to restore (from backup_list)"
|
|
5464
|
+
}
|
|
5465
|
+
},
|
|
5466
|
+
required: ["backup_id"]
|
|
5467
|
+
}
|
|
5468
|
+
},
|
|
5378
5469
|
{
|
|
5379
5470
|
name: "consolidation_report",
|
|
5380
5471
|
description: "Get a report of what the memory system has been learning. Shows memory strengthening/decay events, edge formation, fact extraction, and maintenance cycles. Use this to understand how your memories are evolving.",
|
|
@@ -5489,6 +5580,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
5489
5580
|
type: "array",
|
|
5490
5581
|
items: { type: "string" },
|
|
5491
5582
|
description: "Optional tags for categorization"
|
|
5583
|
+
},
|
|
5584
|
+
threshold: {
|
|
5585
|
+
type: "number",
|
|
5586
|
+
description: "Semantic similarity threshold for 'context' trigger (0.0-1.0, default: 0.7). Lower values match more broadly, higher values require closer semantic match."
|
|
5492
5587
|
}
|
|
5493
5588
|
},
|
|
5494
5589
|
required: ["content", "trigger_type"]
|
|
@@ -6001,7 +6096,7 @@ To start: cd shodh-memory && cargo run`
|
|
|
6001
6096
|
response += ` │ Tags: ${tags.join(", ")}`;
|
|
6002
6097
|
}
|
|
6003
6098
|
response += `
|
|
6004
|
-
ID: ${result.id
|
|
6099
|
+
ID: ${result.id}`;
|
|
6005
6100
|
return {
|
|
6006
6101
|
content: [{ type: "text", text: response }]
|
|
6007
6102
|
};
|
|
@@ -6028,6 +6123,7 @@ ID: ${result.id.slice(0, 8)}...`;
|
|
|
6028
6123
|
const memories = result.memories || [];
|
|
6029
6124
|
const todos = result.todos || [];
|
|
6030
6125
|
const stats = result.retrieval_stats;
|
|
6126
|
+
const lineage = result.lineage || [];
|
|
6031
6127
|
if (memories.length === 0 && todos.length === 0) {
|
|
6032
6128
|
return {
|
|
6033
6129
|
content: [
|
|
@@ -6084,7 +6180,7 @@ ID: ${result.id.slice(0, 8)}...`;
|
|
|
6084
6180
|
`;
|
|
6085
6181
|
response += ` ${content.slice(0, 200)}${content.length > 200 ? "..." : ""}
|
|
6086
6182
|
`;
|
|
6087
|
-
response += ` ┗━ ${getType(m)}${m.tier ? ` │ ${m.tier}` : ""} │ ${m.id
|
|
6183
|
+
response += ` ┗━ ${getType(m)}${m.tier ? ` │ ${m.tier}` : ""} │ ${m.id}
|
|
6088
6184
|
`;
|
|
6089
6185
|
if (i < memories.length - 1)
|
|
6090
6186
|
response += `
|
|
@@ -6126,12 +6222,34 @@ ID: ${result.id.slice(0, 8)}...`;
|
|
|
6126
6222
|
`;
|
|
6127
6223
|
const graphPct = (stats.graph_weight * 100).toFixed(0);
|
|
6128
6224
|
const semPct = (stats.semantic_weight * 100).toFixed(0);
|
|
6129
|
-
response += ` Graph: ${graphPct}% │ Semantic: ${semPct}% │ Density: ${stats.graph_density.toFixed(2)}
|
|
6225
|
+
response += ` Graph: ${graphPct}% │ Semantic: ${semPct}% │ Density: ${(stats.graph_density ?? 0).toFixed(2)}
|
|
6130
6226
|
`;
|
|
6131
6227
|
response += ` Candidates: ${stats.graph_candidates} graph + ${stats.semantic_candidates} semantic
|
|
6132
6228
|
`;
|
|
6133
6229
|
response += ` Entities: ${stats.entities_activated} │ Time: ${(stats.retrieval_time_us / 1000).toFixed(1)}ms`;
|
|
6134
6230
|
}
|
|
6231
|
+
if (lineage.length > 0) {
|
|
6232
|
+
const idShort = (id) => id;
|
|
6233
|
+
const idToContent = new Map;
|
|
6234
|
+
for (const m of memories) {
|
|
6235
|
+
idToContent.set(m.id, getContent(m).slice(0, 40));
|
|
6236
|
+
}
|
|
6237
|
+
response += `
|
|
6238
|
+
|
|
6239
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6240
|
+
`;
|
|
6241
|
+
response += `\uD83D\uDD17 LINEAGE (${lineage.length} causal edge${lineage.length > 1 ? "s" : ""})
|
|
6242
|
+
`;
|
|
6243
|
+
for (const edge of lineage) {
|
|
6244
|
+
const fromLabel = idToContent.get(edge.from) || idShort(edge.from);
|
|
6245
|
+
const toLabel = idToContent.get(edge.to) || idShort(edge.to);
|
|
6246
|
+
const conf = (edge.confidence * 100).toFixed(0);
|
|
6247
|
+
response += ` ${idShort(edge.from)} ──${edge.relation}──▶ ${idShort(edge.to)} (${conf}%)
|
|
6248
|
+
`;
|
|
6249
|
+
response += ` "${fromLabel}..." → "${toLabel}..."
|
|
6250
|
+
`;
|
|
6251
|
+
}
|
|
6252
|
+
}
|
|
6135
6253
|
return {
|
|
6136
6254
|
content: [{ type: "text", text: response }]
|
|
6137
6255
|
};
|
|
@@ -6303,7 +6421,7 @@ ID: ${result.id.slice(0, 8)}...`;
|
|
|
6303
6421
|
}[getType(m)] || "\uD83D\uDCE6";
|
|
6304
6422
|
response += `${String(i + 1).padStart(2)}. ${typeIcon} ${content.slice(0, 150)}${content.length > 150 ? "..." : ""}
|
|
6305
6423
|
`;
|
|
6306
|
-
response += ` ┗━ ${getType(m)}${m.tier ? ` │ ${m.tier}` : ""} │ ${m.id
|
|
6424
|
+
response += ` ┗━ ${getType(m)}${m.tier ? ` │ ${m.tier}` : ""} │ ${m.id}
|
|
6307
6425
|
`;
|
|
6308
6426
|
}
|
|
6309
6427
|
return {
|
|
@@ -6317,7 +6435,7 @@ ID: ${result.id.slice(0, 8)}...`;
|
|
|
6317
6435
|
`;
|
|
6318
6436
|
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6319
6437
|
`;
|
|
6320
|
-
response += `✓ Removed: ${id
|
|
6438
|
+
response += `✓ Removed: ${id}`;
|
|
6321
6439
|
return {
|
|
6322
6440
|
content: [{ type: "text", text: response }]
|
|
6323
6441
|
};
|
|
@@ -6463,7 +6581,7 @@ By Type:
|
|
|
6463
6581
|
response += `Created: ${new Date(b.created_at).toLocaleString()}
|
|
6464
6582
|
`;
|
|
6465
6583
|
} else {
|
|
6466
|
-
response += `✗ Failed: ${result.message}
|
|
6584
|
+
response += `✗ Failed: ${result.message || "Unknown backup creation error"}
|
|
6467
6585
|
`;
|
|
6468
6586
|
}
|
|
6469
6587
|
return {
|
|
@@ -6520,7 +6638,7 @@ By Type:
|
|
|
6520
6638
|
`;
|
|
6521
6639
|
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6522
6640
|
`;
|
|
6523
|
-
response += result.message;
|
|
6641
|
+
response += result.message || "No verification details provided";
|
|
6524
6642
|
return {
|
|
6525
6643
|
content: [{ type: "text", text: response }]
|
|
6526
6644
|
};
|
|
@@ -6546,6 +6664,32 @@ By Type:
|
|
|
6546
6664
|
content: [{ type: "text", text: response }]
|
|
6547
6665
|
};
|
|
6548
6666
|
}
|
|
6667
|
+
case "backup_restore": {
|
|
6668
|
+
const { backup_id } = args;
|
|
6669
|
+
const result = await apiCall("/api/backup/restore", "POST", {
|
|
6670
|
+
user_id: USER_ID,
|
|
6671
|
+
backup_id
|
|
6672
|
+
});
|
|
6673
|
+
let response = `\uD83D\uDD04 Backup Restore
|
|
6674
|
+
`;
|
|
6675
|
+
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6676
|
+
`;
|
|
6677
|
+
if (result.success) {
|
|
6678
|
+
response += `✓ Backup #${backup_id} restored successfully
|
|
6679
|
+
`;
|
|
6680
|
+
if (result.restored_stores.length > 0) {
|
|
6681
|
+
response += `Restored stores: ${result.restored_stores.join(", ")}
|
|
6682
|
+
`;
|
|
6683
|
+
}
|
|
6684
|
+
response += `
|
|
6685
|
+
⚠️ ${result.message || "Restore completed with no additional details"}`;
|
|
6686
|
+
} else {
|
|
6687
|
+
response += `✗ Restore failed: ${result.message || "Unknown restore error"}`;
|
|
6688
|
+
}
|
|
6689
|
+
return {
|
|
6690
|
+
content: [{ type: "text", text: response }]
|
|
6691
|
+
};
|
|
6692
|
+
}
|
|
6549
6693
|
case "proactive_context": {
|
|
6550
6694
|
const {
|
|
6551
6695
|
context,
|
|
@@ -6556,9 +6700,17 @@ By Type:
|
|
|
6556
6700
|
memory_types = [],
|
|
6557
6701
|
auto_ingest = true
|
|
6558
6702
|
} = args;
|
|
6703
|
+
const cleanedContext = stripSystemNoise(context).slice(0, MAX_CONTEXT_LENGTH);
|
|
6704
|
+
if (cleanedContext.length < PROACTIVE_MIN_CONTEXT_LENGTH) {
|
|
6705
|
+
return {
|
|
6706
|
+
content: [{ type: "text", text: `No relevant memories surfaced (context too short after cleaning).
|
|
6707
|
+
|
|
6708
|
+
[Latency: 0.0ms]` }]
|
|
6709
|
+
};
|
|
6710
|
+
}
|
|
6559
6711
|
const result = await apiCall("/api/proactive_context", "POST", {
|
|
6560
6712
|
user_id: USER_ID,
|
|
6561
|
-
context,
|
|
6713
|
+
context: cleanedContext,
|
|
6562
6714
|
max_results,
|
|
6563
6715
|
semantic_threshold,
|
|
6564
6716
|
entity_match_weight,
|
|
@@ -6566,7 +6718,7 @@ By Type:
|
|
|
6566
6718
|
memory_types,
|
|
6567
6719
|
auto_ingest,
|
|
6568
6720
|
previous_response: lastProactiveResponse || undefined,
|
|
6569
|
-
user_followup: lastProactiveResponse ?
|
|
6721
|
+
user_followup: lastProactiveResponse ? cleanedContext : undefined
|
|
6570
6722
|
});
|
|
6571
6723
|
const memories = result.memories || [];
|
|
6572
6724
|
const entities = result.detected_entities || [];
|
|
@@ -6579,7 +6731,7 @@ Detected entities: ${entities.map((e) => `"${e.name}" (${e.entity_type})`).join(
|
|
|
6579
6731
|
[Feedback: ${result.feedback_processed.memories_evaluated} evaluated, ${result.feedback_processed.reinforced.length} reinforced, ${result.feedback_processed.weakened.length} weakened]` : "";
|
|
6580
6732
|
const emptyText = `No relevant memories surfaced for this context.${entityList}${feedbackNote2}
|
|
6581
6733
|
|
|
6582
|
-
[Latency: ${result.latency_ms.toFixed(1)}ms]`;
|
|
6734
|
+
[Latency: ${(result.latency_ms ?? 0).toFixed(1)}ms]`;
|
|
6583
6735
|
lastProactiveResponse = emptyText;
|
|
6584
6736
|
return {
|
|
6585
6737
|
content: [{ type: "text", text: emptyText }]
|
|
@@ -6605,7 +6757,7 @@ Detected entities: ${entities.map((e) => `"${e.name}" (${e.entity_type})`).join(
|
|
|
6605
6757
|
for (const r of uniqueReminders) {
|
|
6606
6758
|
const icon = r.overdue_seconds && r.overdue_seconds > 0 ? "⏰" : "\uD83D\uDCCC";
|
|
6607
6759
|
const contentText = r.content.slice(0, 38);
|
|
6608
|
-
reminderBlock += `┃ ${icon} ${contentText.padEnd(44)} [${r.id
|
|
6760
|
+
reminderBlock += `┃ ${icon} ${contentText.padEnd(44)} [${r.id}] ┃
|
|
6609
6761
|
`;
|
|
6610
6762
|
if (r.overdue_seconds && r.overdue_seconds > 0) {
|
|
6611
6763
|
const mins = Math.round(r.overdue_seconds / 60);
|
|
@@ -6701,7 +6853,7 @@ Detected entities: ${entities.map((e) => `"${e.name}" (${e.entity_type})`).join(
|
|
|
6701
6853
|
const feedbackNote = result.feedback_processed ? `
|
|
6702
6854
|
[Feedback loop: ${result.feedback_processed.memories_evaluated} evaluated, ${result.feedback_processed.reinforced.length} reinforced, ${result.feedback_processed.weakened.length} weakened]` : "";
|
|
6703
6855
|
const ingestNote = result.ingested_memory_id ? `
|
|
6704
|
-
[Context ingested: ${result.ingested_memory_id
|
|
6856
|
+
[Context ingested: ${result.ingested_memory_id}]` : "";
|
|
6705
6857
|
const summaryParts = [];
|
|
6706
6858
|
if (memories.length > 0)
|
|
6707
6859
|
summaryParts.push(`${memories.length} memories`);
|
|
@@ -6716,7 +6868,7 @@ Detected entities: ${entities.map((e) => `"${e.name}" (${e.entity_type})`).join(
|
|
|
6716
6868
|
|
|
6717
6869
|
${formattedWithTime}${entitySummary}${factsBlock}${reminderBlock}${todoBlock}${feedbackNote}${ingestNote}
|
|
6718
6870
|
|
|
6719
|
-
[Latency: ${result.latency_ms.toFixed(1)}ms | Threshold: ${(semantic_threshold * 100).toFixed(0)}%]`;
|
|
6871
|
+
[Latency: ${(result.latency_ms ?? 0).toFixed(1)}ms | Threshold: ${(semantic_threshold * 100).toFixed(0)}%]`;
|
|
6720
6872
|
lastProactiveResponse = responseText;
|
|
6721
6873
|
return {
|
|
6722
6874
|
content: [{ type: "text", text: responseText }]
|
|
@@ -6855,7 +7007,7 @@ ${formattedWithTime}${entitySummary}${factsBlock}${reminderBlock}${todoBlock}${f
|
|
|
6855
7007
|
};
|
|
6856
7008
|
}
|
|
6857
7009
|
case "set_reminder": {
|
|
6858
|
-
const { content, trigger_type, trigger_at, after_seconds, keywords, priority = 3, tags = [] } = args;
|
|
7010
|
+
const { content, trigger_type, trigger_at, after_seconds, keywords, priority = 3, tags = [], threshold } = args;
|
|
6859
7011
|
if (!content || content.length === 0) {
|
|
6860
7012
|
return { content: [{ type: "text", text: "Error: 'content' is required and cannot be empty" }], isError: true };
|
|
6861
7013
|
}
|
|
@@ -6892,7 +7044,8 @@ ${formattedWithTime}${entitySummary}${factsBlock}${reminderBlock}${todoBlock}${f
|
|
|
6892
7044
|
isError: true
|
|
6893
7045
|
};
|
|
6894
7046
|
}
|
|
6895
|
-
|
|
7047
|
+
const ctxThreshold = threshold !== undefined && threshold >= 0 && threshold <= 1 ? threshold : 0.7;
|
|
7048
|
+
trigger = { type: "context", keywords, threshold: ctxThreshold };
|
|
6896
7049
|
break;
|
|
6897
7050
|
default:
|
|
6898
7051
|
return {
|
|
@@ -6911,7 +7064,7 @@ ${formattedWithTime}${entitySummary}${factsBlock}${reminderBlock}${todoBlock}${f
|
|
|
6911
7064
|
`;
|
|
6912
7065
|
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
6913
7066
|
`;
|
|
6914
|
-
response += `ID: ${result.id
|
|
7067
|
+
response += `ID: ${result.id}
|
|
6915
7068
|
`;
|
|
6916
7069
|
response += `Content: ${content}
|
|
6917
7070
|
`;
|
|
@@ -6954,7 +7107,7 @@ ${formattedWithTime}${entitySummary}${factsBlock}${reminderBlock}${todoBlock}${f
|
|
|
6954
7107
|
const statusBadge = r.status === "triggered" ? " [TRIGGERED]" : "";
|
|
6955
7108
|
response += `${icon} ${r.content.slice(0, 50)}${r.content.length > 50 ? "..." : ""}${statusBadge}
|
|
6956
7109
|
`;
|
|
6957
|
-
response += ` Type: ${r.trigger_type} | Priority: ${"★".repeat(r.priority)} | ID: ${r.id
|
|
7110
|
+
response += ` Type: ${r.trigger_type} | Priority: ${"★".repeat(r.priority)} | ID: ${r.id}
|
|
6958
7111
|
`;
|
|
6959
7112
|
if (r.due_at) {
|
|
6960
7113
|
response += ` Due: ${new Date(r.due_at).toLocaleString()}
|
|
@@ -6981,7 +7134,7 @@ ${formattedWithTime}${entitySummary}${factsBlock}${reminderBlock}${todoBlock}${f
|
|
|
6981
7134
|
content: [
|
|
6982
7135
|
{
|
|
6983
7136
|
type: "text",
|
|
6984
|
-
text: result.success ? `✓ Reminder dismissed: ${reminder_id
|
|
7137
|
+
text: result.success ? `✓ Reminder dismissed: ${reminder_id}` : `⚠️ ${result.message || "No message returned"}`
|
|
6985
7138
|
}
|
|
6986
7139
|
]
|
|
6987
7140
|
};
|
|
@@ -7199,7 +7352,13 @@ ${formattedWithTime}${entitySummary}${factsBlock}${reminderBlock}${todoBlock}${f
|
|
|
7199
7352
|
};
|
|
7200
7353
|
}
|
|
7201
7354
|
case "read_memory": {
|
|
7202
|
-
const
|
|
7355
|
+
const memory_id = args.memory_id || args.id;
|
|
7356
|
+
if (!memory_id || typeof memory_id !== "string" || memory_id.trim().length === 0) {
|
|
7357
|
+
return {
|
|
7358
|
+
content: [{ type: "text", text: "Error: 'memory_id' is required. Pass the full UUID or 8+ character prefix from recall results." }],
|
|
7359
|
+
isError: true
|
|
7360
|
+
};
|
|
7361
|
+
}
|
|
7203
7362
|
let memory = null;
|
|
7204
7363
|
try {
|
|
7205
7364
|
memory = await apiCall(`/api/memory/${memory_id}?user_id=${encodeURIComponent(USER_ID)}`, "GET");
|
|
@@ -7222,11 +7381,11 @@ ${formattedWithTime}${entitySummary}${factsBlock}${reminderBlock}${todoBlock}${f
|
|
|
7222
7381
|
response += `Tier: ${memory.tier || "Unknown"} | Created: ${created} | Importance: ${(memory.importance * 100).toFixed(0)}%
|
|
7223
7382
|
`;
|
|
7224
7383
|
if (memory.parent_id) {
|
|
7225
|
-
response += `Parent: ${memory.parent_id
|
|
7384
|
+
response += `Parent: ${memory.parent_id}
|
|
7226
7385
|
`;
|
|
7227
7386
|
}
|
|
7228
7387
|
if (memory.children_count > 0) {
|
|
7229
|
-
response += `Children: ${memory.children_count} (${memory.children_ids.
|
|
7388
|
+
response += `Children: ${memory.children_count} (${memory.children_ids.join(", ")})
|
|
7230
7389
|
`;
|
|
7231
7390
|
}
|
|
7232
7391
|
response += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
@@ -7878,14 +8037,14 @@ function getBinaryPath() {
|
|
|
7878
8037
|
wrapperName = "shodh-memory";
|
|
7879
8038
|
fallbackName = "shodh-memory-server";
|
|
7880
8039
|
}
|
|
7881
|
-
const wrapperPath = path.join(binDir, wrapperName);
|
|
7882
|
-
if (fs.existsSync(wrapperPath)) {
|
|
7883
|
-
return wrapperPath;
|
|
7884
|
-
}
|
|
7885
8040
|
const binaryPath = path.join(binDir, fallbackName);
|
|
7886
8041
|
if (fs.existsSync(binaryPath)) {
|
|
7887
8042
|
return binaryPath;
|
|
7888
8043
|
}
|
|
8044
|
+
const wrapperPath = path.join(binDir, wrapperName);
|
|
8045
|
+
if (fs.existsSync(wrapperPath)) {
|
|
8046
|
+
return wrapperPath;
|
|
8047
|
+
}
|
|
7889
8048
|
return null;
|
|
7890
8049
|
}
|
|
7891
8050
|
async function isServerRunning() {
|
|
@@ -7910,9 +8069,32 @@ async function waitForServer(maxAttempts = 30) {
|
|
|
7910
8069
|
}
|
|
7911
8070
|
return false;
|
|
7912
8071
|
}
|
|
8072
|
+
async function validateApiKey() {
|
|
8073
|
+
try {
|
|
8074
|
+
const controller = new AbortController;
|
|
8075
|
+
const timeout = setTimeout(() => controller.abort(), 3000);
|
|
8076
|
+
const response = await fetch(`${API_URL}/api/health`, {
|
|
8077
|
+
headers: { "X-API-Key": API_KEY },
|
|
8078
|
+
signal: controller.signal
|
|
8079
|
+
});
|
|
8080
|
+
clearTimeout(timeout);
|
|
8081
|
+
return response.ok;
|
|
8082
|
+
} catch {
|
|
8083
|
+
return false;
|
|
8084
|
+
}
|
|
8085
|
+
}
|
|
7913
8086
|
async function ensureServerRunning() {
|
|
7914
8087
|
if (await isServerRunning()) {
|
|
7915
8088
|
console.error("[shodh-memory] Backend server already running at", API_URL);
|
|
8089
|
+
if (!process.env.SHODH_API_KEY && isLocalServer()) {
|
|
8090
|
+
const keyWorks = await validateApiKey();
|
|
8091
|
+
if (!keyWorks) {
|
|
8092
|
+
console.error("[shodh-memory] WARNING: Auto-generated key rejected by running server.");
|
|
8093
|
+
console.error("[shodh-memory] The server was started with a different API key.");
|
|
8094
|
+
console.error("[shodh-memory] Set SHODH_API_KEY to match the server's key, or restart");
|
|
8095
|
+
console.error("[shodh-memory] the server without SHODH_DEV_API_KEY to use auto-generated keys.");
|
|
8096
|
+
}
|
|
8097
|
+
}
|
|
7916
8098
|
return;
|
|
7917
8099
|
}
|
|
7918
8100
|
if (!AUTO_SPAWN_ENABLED) {
|
|
@@ -7930,6 +8112,13 @@ async function ensureServerRunning() {
|
|
|
7930
8112
|
console.error("[shodh-memory] Or download from: https://github.com/varun29ankuS/shodh-memory/releases");
|
|
7931
8113
|
return;
|
|
7932
8114
|
}
|
|
8115
|
+
const expectedBinDir = fs.realpathSync(path.join(__dirname2, "..", "bin"));
|
|
8116
|
+
const resolvedBinary = fs.realpathSync(binaryPath);
|
|
8117
|
+
if (!resolvedBinary.startsWith(expectedBinDir + path.sep) && resolvedBinary !== expectedBinDir) {
|
|
8118
|
+
console.error(`[shodh-memory] WARNING: Binary path resolves outside expected directory: ${resolvedBinary}`);
|
|
8119
|
+
console.error(`[shodh-memory] Expected: ${expectedBinDir}`);
|
|
8120
|
+
return;
|
|
8121
|
+
}
|
|
7933
8122
|
console.error("[shodh-memory] Starting backend server...");
|
|
7934
8123
|
const serverEnv = {};
|
|
7935
8124
|
const SERVER_ENV_ALLOWLIST = new Set([
|
|
@@ -7966,10 +8155,12 @@ async function ensureServerRunning() {
|
|
|
7966
8155
|
}
|
|
7967
8156
|
}
|
|
7968
8157
|
serverEnv["SHODH_DEV_API_KEY"] = API_KEY;
|
|
8158
|
+
const isBat = binaryPath.endsWith(".bat");
|
|
7969
8159
|
serverProcess = spawn(binaryPath, [], {
|
|
7970
8160
|
detached: true,
|
|
7971
8161
|
stdio: "ignore",
|
|
7972
|
-
env: serverEnv
|
|
8162
|
+
env: serverEnv,
|
|
8163
|
+
...isBat && { shell: true }
|
|
7973
8164
|
});
|
|
7974
8165
|
serverProcess.unref();
|
|
7975
8166
|
console.error("[shodh-memory] Waiting for server to start...");
|
|
@@ -8005,14 +8196,23 @@ process.on("SIGTERM", () => {
|
|
|
8005
8196
|
cleanupServer();
|
|
8006
8197
|
process.exit(0);
|
|
8007
8198
|
});
|
|
8199
|
+
function createSandboxServer() {
|
|
8200
|
+
process.env.SMITHERY_SANDBOX = "true";
|
|
8201
|
+
return server;
|
|
8202
|
+
}
|
|
8008
8203
|
async function main() {
|
|
8204
|
+
if (SANDBOX_MODE)
|
|
8205
|
+
return;
|
|
8009
8206
|
await ensureServerRunning();
|
|
8010
8207
|
const transport = new StdioServerTransport;
|
|
8011
8208
|
await server.connect(transport);
|
|
8012
|
-
console.error("Shodh-Memory MCP server v0.1.
|
|
8209
|
+
console.error("Shodh-Memory MCP server v0.1.90 running");
|
|
8013
8210
|
console.error(`Connecting to: ${API_URL}`);
|
|
8014
8211
|
console.error(`User ID: ${USER_ID}`);
|
|
8015
8212
|
console.error(`Streaming: ${STREAM_ENABLED ? "enabled" : "disabled"}`);
|
|
8016
8213
|
console.error(`Proactive surfacing: ${PROACTIVE_SURFACING ? "enabled" : "disabled (SHODH_PROACTIVE=false)"}`);
|
|
8017
8214
|
}
|
|
8018
8215
|
main().catch(console.error);
|
|
8216
|
+
export {
|
|
8217
|
+
createSandboxServer
|
|
8218
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shodh/memory-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.90",
|
|
4
4
|
"mcpName": "io.github.varun29ankuS/shodh-memory",
|
|
5
5
|
"description": "MCP server for persistent AI memory - store and recall context across sessions",
|
|
6
6
|
"type": "module",
|
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
"scripts": {
|
|
12
12
|
"start": "bun run index.ts",
|
|
13
13
|
"build": "bun build index.ts --outdir dist --target node",
|
|
14
|
+
"test": "vitest run",
|
|
15
|
+
"test:watch": "vitest",
|
|
16
|
+
"test:coverage": "vitest run --coverage",
|
|
14
17
|
"postinstall": "node scripts/postinstall.cjs",
|
|
15
18
|
"prepublishOnly": "npm run build"
|
|
16
19
|
},
|
|
@@ -22,6 +25,10 @@
|
|
|
22
25
|
"dependencies": {
|
|
23
26
|
"@modelcontextprotocol/sdk": "^1.24.0"
|
|
24
27
|
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@vitest/coverage-v8": "^2.1.9",
|
|
30
|
+
"vitest": "^2.1.8"
|
|
31
|
+
},
|
|
25
32
|
"keywords": [
|
|
26
33
|
"mcp",
|
|
27
34
|
"model-context-protocol",
|
package/scripts/postinstall.cjs
CHANGED
|
@@ -11,7 +11,7 @@ const path = require('path');
|
|
|
11
11
|
const https = require('https');
|
|
12
12
|
const { execSync } = require('child_process');
|
|
13
13
|
|
|
14
|
-
const VERSION = '
|
|
14
|
+
const VERSION = require('../package.json').version;
|
|
15
15
|
const REPO = 'varun29ankuS/shodh-memory';
|
|
16
16
|
const BIN_DIR = path.join(__dirname, '..', 'bin');
|
|
17
17
|
|