universal-memory-mcp 0.2.3 → 0.3.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/package.json +2 -2
- package/scripts/postinstall.js +123 -6
- package/scripts/universal-memory-stop-hook.mjs +261 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "universal-memory-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "MCP Server for persistent AI memory across sessions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
28
|
-
"universal-memory-core": "^0.1.
|
|
28
|
+
"universal-memory-core": "^0.1.4"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@types/node": "^20.11.0",
|
package/scripts/postinstall.js
CHANGED
|
@@ -12,8 +12,10 @@ import fs from 'fs';
|
|
|
12
12
|
import path from 'path';
|
|
13
13
|
import os from 'os';
|
|
14
14
|
|
|
15
|
-
const
|
|
16
|
-
const
|
|
15
|
+
const CLAUDE_DIR = path.join(os.homedir(), '.claude');
|
|
16
|
+
const CLAUDE_SETTINGS_PATH = path.join(CLAUDE_DIR, 'settings.json');
|
|
17
|
+
const CLAUDE_SKILLS_PATH = path.join(CLAUDE_DIR, 'skills');
|
|
18
|
+
const CLAUDE_HOOKS_PATH = path.join(CLAUDE_DIR, 'hooks');
|
|
17
19
|
|
|
18
20
|
// MCP server configuration
|
|
19
21
|
const MCP_CONFIG = {
|
|
@@ -155,6 +157,21 @@ memory_update_long_term({
|
|
|
155
157
|
- Use project name when in project context
|
|
156
158
|
`;
|
|
157
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Check if Claude Code is installed
|
|
162
|
+
*/
|
|
163
|
+
function checkClaudeCodeInstalled() {
|
|
164
|
+
if (!fs.existsSync(CLAUDE_DIR)) {
|
|
165
|
+
console.log('\n⚠️ Claude Code not detected!\n');
|
|
166
|
+
console.log('Please install Claude Code first:');
|
|
167
|
+
console.log(' https://code.claude.com/\n');
|
|
168
|
+
console.log('After installing Claude Code, run:');
|
|
169
|
+
console.log(' npm install -g universal-memory-mcp\n');
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
|
|
158
175
|
/**
|
|
159
176
|
* Read JSON file safely
|
|
160
177
|
*/
|
|
@@ -253,6 +270,92 @@ function installSkill() {
|
|
|
253
270
|
return true;
|
|
254
271
|
}
|
|
255
272
|
|
|
273
|
+
/**
|
|
274
|
+
* Install Stop hook script
|
|
275
|
+
*/
|
|
276
|
+
function installStopHook() {
|
|
277
|
+
console.log('\n🪝 Installing Stop hook...');
|
|
278
|
+
|
|
279
|
+
const hookScriptPath = path.join(CLAUDE_HOOKS_PATH, 'universal-memory-stop-hook.mjs');
|
|
280
|
+
|
|
281
|
+
// Create hooks directory if not exists
|
|
282
|
+
if (!fs.existsSync(CLAUDE_HOOKS_PATH)) {
|
|
283
|
+
fs.mkdirSync(CLAUDE_HOOKS_PATH, { recursive: true });
|
|
284
|
+
console.log(' Created hooks directory');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Get source script path (in the same directory as this postinstall script)
|
|
288
|
+
const sourceScript = new URL('universal-memory-stop-hook.mjs', import.meta.url).pathname;
|
|
289
|
+
|
|
290
|
+
// Check if hook already exists
|
|
291
|
+
if (fs.existsSync(hookScriptPath)) {
|
|
292
|
+
const existingContent = fs.readFileSync(hookScriptPath, 'utf-8');
|
|
293
|
+
const newContent = fs.readFileSync(sourceScript, 'utf-8');
|
|
294
|
+
|
|
295
|
+
if (existingContent === newContent) {
|
|
296
|
+
console.log(' Stop hook already installed (same version)');
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Backup existing hook
|
|
301
|
+
const backupPath = `${hookScriptPath}.backup.${Date.now()}`;
|
|
302
|
+
fs.copyFileSync(hookScriptPath, backupPath);
|
|
303
|
+
console.log(` Backed up existing hook to: ${backupPath}`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
fs.copyFileSync(sourceScript, hookScriptPath);
|
|
307
|
+
fs.chmodSync(hookScriptPath, 0o755); // Make executable
|
|
308
|
+
console.log(' Stop hook installed successfully');
|
|
309
|
+
return true;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Configure Stop hook in Claude settings
|
|
314
|
+
*/
|
|
315
|
+
function configureStopHook() {
|
|
316
|
+
console.log('\n⚙️ Configuring Stop hook...');
|
|
317
|
+
|
|
318
|
+
let settings = readJsonFile(CLAUDE_SETTINGS_PATH) || {};
|
|
319
|
+
|
|
320
|
+
// Initialize hooks if not exists
|
|
321
|
+
if (!settings.hooks) {
|
|
322
|
+
settings.hooks = {};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Initialize Stop hook array if not exists
|
|
326
|
+
if (!settings.hooks.Stop) {
|
|
327
|
+
settings.hooks.Stop = [];
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Check if our hook is already configured
|
|
331
|
+
const hookScriptPath = path.join(os.homedir(), '.claude', 'hooks', 'universal-memory-stop-hook.mjs');
|
|
332
|
+
const hookCommand = `node ${hookScriptPath}`;
|
|
333
|
+
const alreadyConfigured = settings.hooks.Stop.some(entry =>
|
|
334
|
+
entry.hooks?.some(hook =>
|
|
335
|
+
hook.type === 'command' && hook.command.includes('universal-memory-stop-hook')
|
|
336
|
+
)
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
if (alreadyConfigured) {
|
|
340
|
+
console.log(' Stop hook already configured');
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Add Stop hook configuration
|
|
345
|
+
settings.hooks.Stop.push({
|
|
346
|
+
hooks: [
|
|
347
|
+
{
|
|
348
|
+
type: 'command',
|
|
349
|
+
command: hookCommand
|
|
350
|
+
}
|
|
351
|
+
]
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
writeJsonFile(CLAUDE_SETTINGS_PATH, settings);
|
|
355
|
+
console.log(' Stop hook configured successfully');
|
|
356
|
+
return true;
|
|
357
|
+
}
|
|
358
|
+
|
|
256
359
|
/**
|
|
257
360
|
* Main installation
|
|
258
361
|
*/
|
|
@@ -261,26 +364,39 @@ function main() {
|
|
|
261
364
|
console.log('║ Universal Memory MCP - Setup ║');
|
|
262
365
|
console.log('╚════════════════════════════════════════════════════════════╝');
|
|
263
366
|
|
|
367
|
+
// Check Claude Code installation
|
|
368
|
+
if (!checkClaudeCodeInstalled()) {
|
|
369
|
+
process.exit(0);
|
|
370
|
+
}
|
|
371
|
+
|
|
264
372
|
let needsRestart = false;
|
|
265
373
|
|
|
266
374
|
try {
|
|
267
|
-
// Configure MCP server
|
|
375
|
+
// 1. Configure MCP server
|
|
268
376
|
const mcpConfigured = configureMcpServer();
|
|
269
377
|
if (mcpConfigured) needsRestart = true;
|
|
270
378
|
|
|
271
|
-
// Install skill
|
|
379
|
+
// 2. Install skill
|
|
272
380
|
const skillInstalled = installSkill();
|
|
273
381
|
if (skillInstalled) needsRestart = true;
|
|
274
382
|
|
|
383
|
+
// 3. Install Stop hook script
|
|
384
|
+
const hookInstalled = installStopHook();
|
|
385
|
+
if (hookInstalled) needsRestart = true;
|
|
386
|
+
|
|
387
|
+
// 4. Configure Stop hook
|
|
388
|
+
const hookConfigured = configureStopHook();
|
|
389
|
+
if (hookConfigured) needsRestart = true;
|
|
390
|
+
|
|
275
391
|
// Summary
|
|
276
392
|
console.log('\n' + '═'.repeat(60));
|
|
277
393
|
|
|
278
394
|
if (needsRestart) {
|
|
279
395
|
console.log('\n✅ Setup complete!\n');
|
|
280
|
-
console.log('⚠️ IMPORTANT: Please restart Claude Code to enable
|
|
396
|
+
console.log('⚠️ IMPORTANT: Please restart Claude Code to enable all features.\n');
|
|
281
397
|
console.log('After restart, Claude will automatically:');
|
|
282
398
|
console.log(' • Search past conversations when you reference them');
|
|
283
|
-
console.log(' • Record
|
|
399
|
+
console.log(' • Record EVERY conversation automatically (via Stop hook)');
|
|
284
400
|
console.log(' • Remember your preferences and decisions\n');
|
|
285
401
|
} else {
|
|
286
402
|
console.log('\n✅ Already configured! No changes needed.\n');
|
|
@@ -289,6 +405,7 @@ function main() {
|
|
|
289
405
|
console.log('📁 Configuration locations:');
|
|
290
406
|
console.log(` MCP config: ${CLAUDE_SETTINGS_PATH}`);
|
|
291
407
|
console.log(` Skill: ${path.join(CLAUDE_SKILLS_PATH, 'memory-assistant', 'SKILL.md')}`);
|
|
408
|
+
console.log(` Stop hook: ${path.join(CLAUDE_HOOKS_PATH, 'universal-memory-stop-hook.mjs')}`);
|
|
292
409
|
console.log(` Memory storage: ${path.join(os.homedir(), '.ai_memory')}\n`);
|
|
293
410
|
|
|
294
411
|
} catch (error) {
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { spawnSync } from 'node:child_process';
|
|
6
|
+
|
|
7
|
+
// Enable debug logging via environment variable
|
|
8
|
+
const DEBUG = process.env.UNIVERSAL_MEMORY_DEBUG === '1';
|
|
9
|
+
|
|
10
|
+
function debugLog(message) {
|
|
11
|
+
if (DEBUG) {
|
|
12
|
+
fs.appendFileSync('/tmp/universal-memory-stop-hook.log', `[${new Date().toISOString()}] ${message}\n`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function readStdinSync() {
|
|
17
|
+
return fs.readFileSync(0, 'utf8');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function isNonEmptyString(value) {
|
|
21
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function contentToText(content) {
|
|
25
|
+
if (typeof content === 'string') return content;
|
|
26
|
+
if (Array.isArray(content)) {
|
|
27
|
+
return content
|
|
28
|
+
.map((item) => {
|
|
29
|
+
if (typeof item === 'string') return item;
|
|
30
|
+
if (item && typeof item === 'object' && typeof item.text === 'string') return item.text;
|
|
31
|
+
if (item && typeof item === 'object' && typeof item.content === 'string') return item.content;
|
|
32
|
+
return '';
|
|
33
|
+
})
|
|
34
|
+
.filter(Boolean)
|
|
35
|
+
.join('\n');
|
|
36
|
+
}
|
|
37
|
+
if (content && typeof content === 'object') {
|
|
38
|
+
if (typeof content.text === 'string') return content.text;
|
|
39
|
+
if (typeof content.content === 'string') return content.content;
|
|
40
|
+
}
|
|
41
|
+
return '';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function extractMessages(transcriptJson) {
|
|
45
|
+
if (!transcriptJson || typeof transcriptJson !== 'object') return [];
|
|
46
|
+
|
|
47
|
+
if (Array.isArray(transcriptJson.messages)) return transcriptJson.messages;
|
|
48
|
+
if (Array.isArray(transcriptJson.turns)) return transcriptJson.turns;
|
|
49
|
+
if (Array.isArray(transcriptJson.events)) return transcriptJson.events;
|
|
50
|
+
if (Array.isArray(transcriptJson.transcript)) return transcriptJson.transcript;
|
|
51
|
+
|
|
52
|
+
if (transcriptJson.conversation && Array.isArray(transcriptJson.conversation.messages)) {
|
|
53
|
+
return transcriptJson.conversation.messages;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function extractLastExchange(messages) {
|
|
60
|
+
const normalized = messages
|
|
61
|
+
.map((m) => {
|
|
62
|
+
if (!m || typeof m !== 'object') return null;
|
|
63
|
+
const role = m.role || m.author || m.type;
|
|
64
|
+
const content = m.content ?? m.text ?? m.message ?? m.data?.content;
|
|
65
|
+
return {
|
|
66
|
+
role: typeof role === 'string' ? role : '',
|
|
67
|
+
text: contentToText(content),
|
|
68
|
+
};
|
|
69
|
+
})
|
|
70
|
+
.filter(Boolean)
|
|
71
|
+
.filter((m) => isNonEmptyString(m.role) && isNonEmptyString(m.text));
|
|
72
|
+
|
|
73
|
+
const roleNorm = (r) => String(r).toLowerCase();
|
|
74
|
+
const lastUserIdx = (() => {
|
|
75
|
+
for (let i = normalized.length - 1; i >= 0; i--) {
|
|
76
|
+
if (roleNorm(normalized[i].role) === 'user') return i;
|
|
77
|
+
}
|
|
78
|
+
return -1;
|
|
79
|
+
})();
|
|
80
|
+
|
|
81
|
+
const lastAssistantIdxAfterUser = (() => {
|
|
82
|
+
if (lastUserIdx === -1) return -1;
|
|
83
|
+
for (let i = lastUserIdx + 1; i < normalized.length; i++) {
|
|
84
|
+
if (roleNorm(normalized[i].role) === 'assistant') return i;
|
|
85
|
+
}
|
|
86
|
+
return -1;
|
|
87
|
+
})();
|
|
88
|
+
|
|
89
|
+
const lastAssistantIdx = (() => {
|
|
90
|
+
for (let i = normalized.length - 1; i >= 0; i--) {
|
|
91
|
+
if (roleNorm(normalized[i].role) === 'assistant') return i;
|
|
92
|
+
}
|
|
93
|
+
return -1;
|
|
94
|
+
})();
|
|
95
|
+
|
|
96
|
+
const userText = lastUserIdx !== -1 ? normalized[lastUserIdx].text : '';
|
|
97
|
+
const aiText =
|
|
98
|
+
lastAssistantIdxAfterUser !== -1
|
|
99
|
+
? normalized[lastAssistantIdxAfterUser].text
|
|
100
|
+
: lastAssistantIdx !== -1
|
|
101
|
+
? normalized[lastAssistantIdx].text
|
|
102
|
+
: '';
|
|
103
|
+
|
|
104
|
+
return { userText, aiText };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function truncate(text, maxChars) {
|
|
108
|
+
if (!isNonEmptyString(text)) return '';
|
|
109
|
+
if (text.length <= maxChars) return text;
|
|
110
|
+
return text.slice(0, maxChars) + `\n\n[truncated to ${maxChars} chars]`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function findUp(startDir, relativeTarget) {
|
|
114
|
+
let dir = startDir;
|
|
115
|
+
for (;;) {
|
|
116
|
+
const candidate = path.join(dir, relativeTarget);
|
|
117
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
118
|
+
const parent = path.dirname(dir);
|
|
119
|
+
if (parent === dir) return null;
|
|
120
|
+
dir = parent;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function runRecordCommand(payload, cwd) {
|
|
125
|
+
const input = JSON.stringify(payload);
|
|
126
|
+
|
|
127
|
+
const direct = spawnSync('universal-memory-record', ['--json'], {
|
|
128
|
+
input,
|
|
129
|
+
encoding: 'utf8',
|
|
130
|
+
cwd,
|
|
131
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
132
|
+
});
|
|
133
|
+
if (direct.status === 0) return { ok: true, id: direct.stdout.trim() };
|
|
134
|
+
|
|
135
|
+
const npx = spawnSync(
|
|
136
|
+
'npx',
|
|
137
|
+
['-y', '--package', 'universal-memory-mcp', 'universal-memory-record', '--json'],
|
|
138
|
+
{
|
|
139
|
+
input,
|
|
140
|
+
encoding: 'utf8',
|
|
141
|
+
cwd,
|
|
142
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
if (npx.status === 0) return { ok: true, id: npx.stdout.trim() };
|
|
146
|
+
|
|
147
|
+
const distPath = findUp(cwd, path.join('packages', 'mcp-server', 'dist', 'record.js'));
|
|
148
|
+
if (!distPath) {
|
|
149
|
+
return {
|
|
150
|
+
ok: false,
|
|
151
|
+
error: 'universal-memory-record not found (PATH/npx) and dist/record.js not found',
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const fallback = spawnSync('node', [distPath, '--json'], {
|
|
156
|
+
input,
|
|
157
|
+
encoding: 'utf8',
|
|
158
|
+
cwd,
|
|
159
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
160
|
+
});
|
|
161
|
+
if (fallback.status === 0) return { ok: true, id: fallback.stdout.trim() };
|
|
162
|
+
return { ok: false, error: fallback.stderr || fallback.stdout || 'record command failed' };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function detectProjectName(cwd) {
|
|
166
|
+
try {
|
|
167
|
+
const gitDir = findUp(cwd, '.git');
|
|
168
|
+
if (gitDir) return path.basename(path.dirname(gitDir));
|
|
169
|
+
} catch {
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function main() {
|
|
176
|
+
debugLog('Stop hook triggered');
|
|
177
|
+
|
|
178
|
+
const raw = readStdinSync();
|
|
179
|
+
if (!raw.trim()) {
|
|
180
|
+
debugLog('No stdin input, exiting');
|
|
181
|
+
process.exit(0);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
debugLog(`Received input: ${raw.substring(0, 200)}...`);
|
|
185
|
+
|
|
186
|
+
let hookInput;
|
|
187
|
+
try {
|
|
188
|
+
hookInput = JSON.parse(raw);
|
|
189
|
+
} catch (err) {
|
|
190
|
+
debugLog(`Failed to parse input JSON: ${err.message}`);
|
|
191
|
+
process.exit(0);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const cwd = hookInput.cwd || process.cwd();
|
|
195
|
+
const transcriptPath = hookInput.transcript_path || hookInput.transcriptPath;
|
|
196
|
+
const sessionId = hookInput.session_id || hookInput.sessionId;
|
|
197
|
+
|
|
198
|
+
debugLog(`transcript_path: ${transcriptPath}`);
|
|
199
|
+
|
|
200
|
+
if (!isNonEmptyString(transcriptPath)) {
|
|
201
|
+
debugLog('No transcript_path, exiting');
|
|
202
|
+
process.exit(0);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
let messages;
|
|
206
|
+
try {
|
|
207
|
+
const transcriptContent = fs.readFileSync(transcriptPath, 'utf8');
|
|
208
|
+
|
|
209
|
+
// Parse JSONL format (one JSON object per line)
|
|
210
|
+
const lines = transcriptContent.trim().split('\n').filter(line => line.trim());
|
|
211
|
+
const entries = lines.map(line => JSON.parse(line));
|
|
212
|
+
|
|
213
|
+
debugLog(`Successfully read ${entries.length} transcript entries`);
|
|
214
|
+
|
|
215
|
+
// Extract messages from JSONL entries
|
|
216
|
+
messages = entries
|
|
217
|
+
.filter(entry => entry.type === 'user' || entry.type === 'assistant')
|
|
218
|
+
.map(entry => ({
|
|
219
|
+
role: entry.type === 'user' ? 'user' : 'assistant',
|
|
220
|
+
content: entry.message?.content || entry.content
|
|
221
|
+
}));
|
|
222
|
+
|
|
223
|
+
debugLog(`Extracted ${messages.length} messages from JSONL`);
|
|
224
|
+
} catch (err) {
|
|
225
|
+
debugLog(`Failed to read transcript: ${err.message}`);
|
|
226
|
+
process.exit(0);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const { userText, aiText } = extractLastExchange(messages);
|
|
230
|
+
debugLog(`userText length: ${userText.length}, aiText length: ${aiText.length}`);
|
|
231
|
+
|
|
232
|
+
if (!isNonEmptyString(userText) || !isNonEmptyString(aiText)) {
|
|
233
|
+
debugLog('Empty user or AI text, exiting');
|
|
234
|
+
process.exit(0);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Determine client - default to 'claude-code' if not provided
|
|
238
|
+
const client = isNonEmptyString(hookInput.client) ? hookInput.client : 'claude-code';
|
|
239
|
+
|
|
240
|
+
const payload = {
|
|
241
|
+
user_message: truncate(userText, 8000),
|
|
242
|
+
ai_response: truncate(aiText, 20000),
|
|
243
|
+
project: detectProjectName(cwd),
|
|
244
|
+
client: client,
|
|
245
|
+
session_id: isNonEmptyString(sessionId) ? sessionId : undefined,
|
|
246
|
+
working_directory: cwd,
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
debugLog(`Payload: client=${client}, project=${payload.project}, session_id=${payload.session_id}`);
|
|
250
|
+
|
|
251
|
+
const res = runRecordCommand(payload, cwd);
|
|
252
|
+
if (!res.ok) {
|
|
253
|
+
const errorMsg = `universal-memory stop hook warning: ${res.error}\n`;
|
|
254
|
+
process.stderr.write(errorMsg);
|
|
255
|
+
debugLog(`Error: ${res.error}`);
|
|
256
|
+
} else {
|
|
257
|
+
debugLog(`Successfully saved memory: ${res.id}`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
main();
|