collective-memory-mcp 0.1.0 → 0.3.0
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 +104 -22
- package/package.json +12 -9
- package/src/models.js +89 -0
- package/src/server.js +793 -0
- package/src/storage.js +442 -0
- package/index.js +0 -127
package/src/storage.js
ADDED
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage layer for the Collective Memory System using JSON file.
|
|
3
|
+
* Pure JavaScript - no native dependencies required.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { promises as fs } from "fs";
|
|
7
|
+
import path from "path";
|
|
8
|
+
import os from "os";
|
|
9
|
+
import { createHash } from "crypto";
|
|
10
|
+
import { Entity, Relation } from "./models.js";
|
|
11
|
+
|
|
12
|
+
const DB_DIR = path.join(os.homedir(), ".collective-memory");
|
|
13
|
+
const DB_PATH = path.join(DB_DIR, "memory.json");
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Lock file to prevent concurrent writes
|
|
17
|
+
*/
|
|
18
|
+
const LOCK_FILE = path.join(DB_DIR, "memory.lock");
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Simple file-based storage with locking
|
|
22
|
+
*/
|
|
23
|
+
export class Storage {
|
|
24
|
+
constructor(dbPath = DB_PATH) {
|
|
25
|
+
this.dbPath = dbPath;
|
|
26
|
+
this.lockPath = LOCK_FILE;
|
|
27
|
+
this.data = null;
|
|
28
|
+
this.lockRetries = 10;
|
|
29
|
+
this.lockDelay = 50;
|
|
30
|
+
this.init();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Initialize storage
|
|
35
|
+
*/
|
|
36
|
+
async init() {
|
|
37
|
+
await this.ensureDir();
|
|
38
|
+
await this.load();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Ensure data directory exists
|
|
43
|
+
*/
|
|
44
|
+
async ensureDir() {
|
|
45
|
+
try {
|
|
46
|
+
await fs.mkdir(path.dirname(this.dbPath), { recursive: true });
|
|
47
|
+
} catch {
|
|
48
|
+
// Ignore if exists
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Load data from file
|
|
54
|
+
*/
|
|
55
|
+
async load() {
|
|
56
|
+
try {
|
|
57
|
+
const content = await fs.readFile(this.dbPath, "utf-8");
|
|
58
|
+
this.data = JSON.parse(content);
|
|
59
|
+
} catch {
|
|
60
|
+
// File doesn't exist or is invalid - create new
|
|
61
|
+
this.data = {
|
|
62
|
+
entities: {},
|
|
63
|
+
relations: [],
|
|
64
|
+
version: "1.0",
|
|
65
|
+
};
|
|
66
|
+
await this.save();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Save data to file
|
|
72
|
+
*/
|
|
73
|
+
async save() {
|
|
74
|
+
const dir = path.dirname(this.dbPath);
|
|
75
|
+
await fs.mkdir(dir, { recursive: true });
|
|
76
|
+
const tempPath = path.join(dir, ".memory.tmp");
|
|
77
|
+
await fs.writeFile(tempPath, JSON.stringify(this.data, null, 2), "utf-8");
|
|
78
|
+
await fs.rename(tempPath, this.dbPath);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Acquire lock
|
|
83
|
+
*/
|
|
84
|
+
async acquireLock() {
|
|
85
|
+
for (let i = 0; i < this.lockRetries; i++) {
|
|
86
|
+
try {
|
|
87
|
+
await fs.writeFile(
|
|
88
|
+
this.lockPath,
|
|
89
|
+
process.pid.toString(),
|
|
90
|
+
{ flag: "wx" }
|
|
91
|
+
);
|
|
92
|
+
return;
|
|
93
|
+
} catch {
|
|
94
|
+
await new Promise(r => setTimeout(r, this.lockDelay));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// If we can't get lock, just proceed (it's a simple file lock)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Release lock
|
|
102
|
+
*/
|
|
103
|
+
async releaseLock() {
|
|
104
|
+
try {
|
|
105
|
+
await fs.unlink(this.lockPath);
|
|
106
|
+
} catch {
|
|
107
|
+
// Ignore
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Execute operation with lock
|
|
113
|
+
*/
|
|
114
|
+
async withLock(fn) {
|
|
115
|
+
await this.acquireLock();
|
|
116
|
+
try {
|
|
117
|
+
return await fn();
|
|
118
|
+
} finally {
|
|
119
|
+
await this.releaseLock();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ========== Entity Operations ==========
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Create a new entity. Returns true if created, false if duplicate.
|
|
127
|
+
*/
|
|
128
|
+
async createEntity(entity) {
|
|
129
|
+
return this.withLock(async () => {
|
|
130
|
+
if (this.data.entities[entity.name]) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
this.data.entities[entity.name] = entity.toJSON();
|
|
134
|
+
await this.save();
|
|
135
|
+
return true;
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get an entity by name
|
|
141
|
+
*/
|
|
142
|
+
getEntity(name) {
|
|
143
|
+
const data = this.data.entities[name];
|
|
144
|
+
if (data) {
|
|
145
|
+
return new Entity(data);
|
|
146
|
+
}
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Get all entities
|
|
152
|
+
*/
|
|
153
|
+
getAllEntities() {
|
|
154
|
+
return Object.values(this.data.entities).map(data => new Entity(data));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Check if an entity exists
|
|
159
|
+
*/
|
|
160
|
+
entityExists(name) {
|
|
161
|
+
return name in this.data.entities;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Update entity
|
|
166
|
+
*/
|
|
167
|
+
async updateEntity(name, { observations, metadata } = {}) {
|
|
168
|
+
return this.withLock(async () => {
|
|
169
|
+
const entity = this.data.entities[name];
|
|
170
|
+
if (!entity) return false;
|
|
171
|
+
|
|
172
|
+
if (observations !== undefined) {
|
|
173
|
+
entity.observations = observations;
|
|
174
|
+
}
|
|
175
|
+
if (metadata !== undefined) {
|
|
176
|
+
entity.metadata = metadata;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
await this.save();
|
|
180
|
+
return true;
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Delete an entity and its relations
|
|
186
|
+
*/
|
|
187
|
+
async deleteEntity(name) {
|
|
188
|
+
return this.withLock(async () => {
|
|
189
|
+
if (!this.data.entities[name]) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
delete this.data.entities[name];
|
|
194
|
+
|
|
195
|
+
// Remove relations involving this entity
|
|
196
|
+
this.data.relations = this.data.relations.filter(
|
|
197
|
+
r => r.from !== name && r.to !== name
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
await this.save();
|
|
201
|
+
return true;
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Delete multiple entities
|
|
207
|
+
*/
|
|
208
|
+
async deleteEntities(names) {
|
|
209
|
+
let count = 0;
|
|
210
|
+
for (const name of names) {
|
|
211
|
+
if (await this.deleteEntity(name)) count++;
|
|
212
|
+
}
|
|
213
|
+
return count;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ========== Relation Operations ==========
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Create a new relation. Returns true if created, false if duplicate.
|
|
220
|
+
*/
|
|
221
|
+
async createRelation(relation) {
|
|
222
|
+
return this.withLock(async () => {
|
|
223
|
+
const key = this.relationKey(relation.from, relation.to, relation.relationType);
|
|
224
|
+
if (this.data.relations.some(r => this.relationKey(r.from, r.to, r.relationType) === key)) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
this.data.relations.push(relation.toJSON());
|
|
229
|
+
await this.save();
|
|
230
|
+
return true;
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Generate unique key for relation
|
|
236
|
+
*/
|
|
237
|
+
relationKey(from, to, type) {
|
|
238
|
+
return `${from}|${to}|${type}`;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Get relations with optional filters
|
|
243
|
+
*/
|
|
244
|
+
getRelations({ fromEntity, toEntity, relationType } = {}) {
|
|
245
|
+
let results = this.data.relations.map(r => new Relation(r));
|
|
246
|
+
|
|
247
|
+
if (fromEntity) {
|
|
248
|
+
results = results.filter(r => r.from === fromEntity);
|
|
249
|
+
}
|
|
250
|
+
if (toEntity) {
|
|
251
|
+
results = results.filter(r => r.to === toEntity);
|
|
252
|
+
}
|
|
253
|
+
if (relationType) {
|
|
254
|
+
results = results.filter(r => r.relationType === relationType);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return results;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Get all relations
|
|
262
|
+
*/
|
|
263
|
+
getAllRelations() {
|
|
264
|
+
return this.data.relations.map(r => new Relation(r));
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Check if a relation exists
|
|
269
|
+
*/
|
|
270
|
+
relationExists(fromEntity, toEntity, relationType) {
|
|
271
|
+
return this.data.relations.some(
|
|
272
|
+
r => r.from === fromEntity && r.to === toEntity && r.relationType === relationType
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Delete a specific relation
|
|
278
|
+
*/
|
|
279
|
+
async deleteRelation(fromEntity, toEntity, relationType) {
|
|
280
|
+
return this.withLock(async () => {
|
|
281
|
+
const before = this.data.relations.length;
|
|
282
|
+
this.data.relations = this.data.relations.filter(
|
|
283
|
+
r => !(r.from === fromEntity && r.to === toEntity && r.relationType === relationType)
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
if (this.data.relations.length < before) {
|
|
287
|
+
await this.save();
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
return false;
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Delete multiple relations
|
|
296
|
+
*/
|
|
297
|
+
async deleteRelations(relations) {
|
|
298
|
+
let count = 0;
|
|
299
|
+
for (const [fromEntity, toEntity, relationType] of relations) {
|
|
300
|
+
if (await this.deleteRelation(fromEntity, toEntity, relationType)) count++;
|
|
301
|
+
}
|
|
302
|
+
return count;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ========== Search ==========
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Search entities by name, type, or observations
|
|
309
|
+
*/
|
|
310
|
+
searchEntities(query) {
|
|
311
|
+
const lowerQuery = query.toLowerCase();
|
|
312
|
+
return this.getAllEntities().filter(e => {
|
|
313
|
+
if (e.name.toLowerCase().includes(lowerQuery)) return true;
|
|
314
|
+
if (e.entityType.toLowerCase().includes(lowerQuery)) return true;
|
|
315
|
+
if (e.observations.some(o => o.toLowerCase().includes(lowerQuery))) return true;
|
|
316
|
+
return false;
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Get entities related to a given entity
|
|
322
|
+
*/
|
|
323
|
+
getRelatedEntities(entityName) {
|
|
324
|
+
const result = { connected: [], incoming: [], outgoing: [] };
|
|
325
|
+
|
|
326
|
+
const connectedNames = new Set();
|
|
327
|
+
|
|
328
|
+
for (const rel of this.data.relations) {
|
|
329
|
+
if (rel.from === entityName) {
|
|
330
|
+
connectedNames.add(rel.to);
|
|
331
|
+
result.outgoing.push(rel.to);
|
|
332
|
+
}
|
|
333
|
+
if (rel.to === entityName) {
|
|
334
|
+
connectedNames.add(rel.from);
|
|
335
|
+
result.incoming.push(rel.from);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
for (const name of connectedNames) {
|
|
340
|
+
const entity = this.getEntity(name);
|
|
341
|
+
if (entity) {
|
|
342
|
+
result.connected.push(entity);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return result;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Close storage
|
|
351
|
+
*/
|
|
352
|
+
async close() {
|
|
353
|
+
await this.save();
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Sync wrapper for async operations
|
|
358
|
+
let storageInstance = null;
|
|
359
|
+
|
|
360
|
+
export function getStorage(dbPath) {
|
|
361
|
+
if (!storageInstance) {
|
|
362
|
+
storageInstance = new Storage(dbPath);
|
|
363
|
+
// Initialize asynchronously but don't wait
|
|
364
|
+
storageInstance.init().catch(console.error);
|
|
365
|
+
}
|
|
366
|
+
return storageInstance;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Synchronous methods for compatibility
|
|
370
|
+
export class SyncStorage {
|
|
371
|
+
constructor(dbPath = DB_PATH) {
|
|
372
|
+
this.storage = new Storage(dbPath);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
async init() {
|
|
376
|
+
await this.storage.init();
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
createEntity(entity) {
|
|
380
|
+
return this.storage.createEntity(entity);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
getEntity(name) {
|
|
384
|
+
return this.storage.getEntity(name);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
getAllEntities() {
|
|
388
|
+
return this.storage.getAllEntities();
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
entityExists(name) {
|
|
392
|
+
return this.storage.entityExists(name);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
updateEntity(name, data) {
|
|
396
|
+
return this.storage.updateEntity(name, data);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
deleteEntity(name) {
|
|
400
|
+
return this.storage.deleteEntity(name);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
deleteEntities(names) {
|
|
404
|
+
return this.storage.deleteEntities(names);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
createRelation(relation) {
|
|
408
|
+
return this.storage.createRelation(relation);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
getRelations(filters) {
|
|
412
|
+
return this.storage.getRelations(filters);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
getAllRelations() {
|
|
416
|
+
return this.storage.getAllRelations();
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
relationExists(from, to, type) {
|
|
420
|
+
return this.storage.relationExists(from, to, type);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
deleteRelation(from, to, type) {
|
|
424
|
+
return this.storage.deleteRelation(from, to, type);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
deleteRelations(relations) {
|
|
428
|
+
return this.storage.deleteRelations(relations);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
searchEntities(query) {
|
|
432
|
+
return this.storage.searchEntities(query);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
getRelatedEntities(name) {
|
|
436
|
+
return this.storage.getRelatedEntities(name);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
close() {
|
|
440
|
+
return this.storage.close();
|
|
441
|
+
}
|
|
442
|
+
}
|
package/index.js
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Collective Memory MCP Server - npx Wrapper
|
|
5
|
-
*
|
|
6
|
-
* This wrapper spawns the Python MCP server, making it available via npx:
|
|
7
|
-
* npx collective-memory-mcp
|
|
8
|
-
*
|
|
9
|
-
* The Python package will be automatically installed on first run.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { spawn, spawnSync } from 'child_process';
|
|
13
|
-
import { fileURLToPath } from 'url';
|
|
14
|
-
import { dirname, join } from 'path';
|
|
15
|
-
import { existsSync, readFileSync } from 'fs';
|
|
16
|
-
import { homedir } from 'os';
|
|
17
|
-
|
|
18
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
19
|
-
const __dirname = dirname(__filename);
|
|
20
|
-
|
|
21
|
-
// ANSI color codes for better output
|
|
22
|
-
const colors = {
|
|
23
|
-
reset: '\x1b[0m',
|
|
24
|
-
red: '\x1b[31m',
|
|
25
|
-
green: '\x1b[32m',
|
|
26
|
-
yellow: '\x1b[33m',
|
|
27
|
-
blue: '\x1b[34m',
|
|
28
|
-
cyan: '\x1b[36m',
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
function log(message, color = 'reset') {
|
|
32
|
-
console.error(`${colors[color]}${message}${colors.reset}`);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function findPython() {
|
|
36
|
-
// Try different Python commands
|
|
37
|
-
const pythonCommands = ['python3', 'python', 'python3.12', 'python3.11', 'python3.10'];
|
|
38
|
-
|
|
39
|
-
for (const cmd of pythonCommands) {
|
|
40
|
-
const result = spawnSync(cmd, ['--version'], { stdio: 'pipe' });
|
|
41
|
-
if (result.status === 0) {
|
|
42
|
-
return cmd;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function getDbPath() {
|
|
50
|
-
// Allow override via environment variable
|
|
51
|
-
if (process.env.COLLECTIVE_MEMORY_DB_PATH) {
|
|
52
|
-
return process.env.COLLECTIVE_MEMORY_DB_PATH;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Default to ~/.collective-memory/memory.db
|
|
56
|
-
return join(homedir(), '.collective-memory', 'memory.db');
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async function main() {
|
|
60
|
-
const pythonCmd = findPython();
|
|
61
|
-
|
|
62
|
-
if (!pythonCmd) {
|
|
63
|
-
log('Error: Python 3.10+ is required but not found.', 'red');
|
|
64
|
-
log('Please install Python from https://python.org', 'yellow');
|
|
65
|
-
process.exit(1);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Check if collective_memory module is available
|
|
69
|
-
const checkModule = spawnSync(pythonCmd, ['-c', 'import collective_memory'], {
|
|
70
|
-
stdio: 'pipe'
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
if (checkModule.status !== 0) {
|
|
74
|
-
log('Collective Memory MCP Server not found. Installing...', 'yellow');
|
|
75
|
-
log('', 'reset');
|
|
76
|
-
|
|
77
|
-
const installArgs = ['-m', 'pip', 'install', '-U', 'collective-memory-mcp'];
|
|
78
|
-
const install = spawn(pythonCmd, installArgs, {
|
|
79
|
-
stdio: 'inherit'
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
install.on('close', (code) => {
|
|
83
|
-
if (code !== 0) {
|
|
84
|
-
log('', 'reset');
|
|
85
|
-
log('Failed to install collective-memory-mcp Python package.', 'red');
|
|
86
|
-
log('You can install it manually:', 'yellow');
|
|
87
|
-
log(` ${pythonCmd} -m pip install collective-memory-mcp`, 'cyan');
|
|
88
|
-
process.exit(1);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
log('', 'reset');
|
|
92
|
-
log('Installation complete. Starting server...', 'green');
|
|
93
|
-
startServer(pythonCmd);
|
|
94
|
-
});
|
|
95
|
-
} else {
|
|
96
|
-
startServer(pythonCmd);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function startServer(pythonCmd) {
|
|
101
|
-
const dbPath = getDbPath();
|
|
102
|
-
|
|
103
|
-
// Spawn the Python MCP server
|
|
104
|
-
const server = spawn(pythonCmd, ['-m', 'collective_memory'], {
|
|
105
|
-
stdio: 'inherit',
|
|
106
|
-
env: {
|
|
107
|
-
...process.env,
|
|
108
|
-
COLLECTIVE_MEMORY_DB_PATH: dbPath,
|
|
109
|
-
PYTHONUNBUFFERED: '1',
|
|
110
|
-
}
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
server.on('error', (err) => {
|
|
114
|
-
log(`Error starting server: ${err.message}`, 'red');
|
|
115
|
-
process.exit(1);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
server.on('exit', (code) => {
|
|
119
|
-
process.exit(code ?? 0);
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Handle signals
|
|
124
|
-
process.on('SIGINT', () => process.exit(0));
|
|
125
|
-
process.on('SIGTERM', () => process.exit(0));
|
|
126
|
-
|
|
127
|
-
main();
|