claude-mem-lite 2.24.0 → 2.24.2
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/install.mjs +5 -5
- package/mem-cli.mjs +76 -51
- package/package.json +1 -1
- package/scripts/launch.mjs +22 -0
- package/scripts/setup.sh +5 -1
- package/server.mjs +1 -1
package/install.mjs
CHANGED
|
@@ -977,11 +977,11 @@ async function doctor() {
|
|
|
977
977
|
issues++;
|
|
978
978
|
}
|
|
979
979
|
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
ok('@modelcontextprotocol/sdk:
|
|
983
|
-
}
|
|
984
|
-
fail(
|
|
980
|
+
try {
|
|
981
|
+
await import('@modelcontextprotocol/sdk/server/mcp.js');
|
|
982
|
+
ok('@modelcontextprotocol/sdk: verified (import OK)');
|
|
983
|
+
} catch (e) {
|
|
984
|
+
fail(`@modelcontextprotocol/sdk: import failed (${e.message})`);
|
|
985
985
|
issues++;
|
|
986
986
|
}
|
|
987
987
|
|
package/mem-cli.mjs
CHANGED
|
@@ -26,7 +26,7 @@ function parseArgs(argv) {
|
|
|
26
26
|
if (arg.startsWith('--')) {
|
|
27
27
|
const key = arg.slice(2);
|
|
28
28
|
const next = argv[i + 1];
|
|
29
|
-
if (next !== undefined && !next.startsWith('--') && !next.startsWith('-')) {
|
|
29
|
+
if (next !== undefined && !next.startsWith('--') && (!next.startsWith('-') || /^-\d/.test(next))) {
|
|
30
30
|
flags[key] = next;
|
|
31
31
|
i += 2;
|
|
32
32
|
} else {
|
|
@@ -50,6 +50,11 @@ function out(text) {
|
|
|
50
50
|
process.stdout.write(text + '\n');
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
function fail(text) {
|
|
54
|
+
process.stdout.write(text + '\n');
|
|
55
|
+
process.exitCode = 1;
|
|
56
|
+
}
|
|
57
|
+
|
|
53
58
|
function relativeTime(epochMs) {
|
|
54
59
|
const diff = Date.now() - epochMs;
|
|
55
60
|
if (diff < 0) return 'just now';
|
|
@@ -73,7 +78,7 @@ function cmdSearch(db, args) {
|
|
|
73
78
|
const { positional, flags } = parseArgs(args);
|
|
74
79
|
const query = positional.join(' ');
|
|
75
80
|
if (!query) {
|
|
76
|
-
|
|
81
|
+
fail('[mem] Usage: mem search <query> [--type TYPE] [--source SOURCE] [--limit N] [--project P] [--from DATE] [--to DATE] [--importance N] [--branch B] [--offset N] [--sort relevance|time|importance]');
|
|
77
82
|
return;
|
|
78
83
|
}
|
|
79
84
|
|
|
@@ -84,29 +89,35 @@ function cmdSearch(db, args) {
|
|
|
84
89
|
const dateFrom = flags.from ? new Date(flags.from).getTime() : null;
|
|
85
90
|
let dateTo = flags.to ? new Date(flags.to).getTime() : null;
|
|
86
91
|
if (dateTo && flags.to && /^\d{4}-\d{2}-\d{2}$/.test(flags.to)) dateTo += 86400000 - 1;
|
|
87
|
-
if (flags.from && isNaN(dateFrom)) {
|
|
88
|
-
if (flags.to && isNaN(dateTo)) {
|
|
92
|
+
if (flags.from && isNaN(dateFrom)) { fail(`[mem] Invalid --from date: "${flags.from}". Use YYYY-MM-DD or ISO 8601.`); return; }
|
|
93
|
+
if (flags.to && isNaN(dateTo)) { fail(`[mem] Invalid --to date: "${flags.to}". Use YYYY-MM-DD or ISO 8601.`); return; }
|
|
89
94
|
const minImportance = flags.importance ? parseInt(flags.importance, 10) : null;
|
|
90
95
|
const branch = flags.branch || null;
|
|
91
96
|
const offset = Math.max(0, parseInt(flags.offset, 10) || 0);
|
|
92
97
|
const tier = flags.tier || null;
|
|
93
98
|
const sort = flags.sort || 'relevance';
|
|
94
99
|
if (!['relevance', 'time', 'importance'].includes(sort)) {
|
|
95
|
-
|
|
100
|
+
fail(`[mem] Invalid --sort "${sort}". Use: relevance, time, importance`);
|
|
96
101
|
return;
|
|
97
102
|
}
|
|
98
103
|
|
|
99
104
|
if (source && !['observations', 'sessions', 'prompts'].includes(source)) {
|
|
100
|
-
|
|
105
|
+
fail(`[mem] Invalid --source "${source}". Use: observations, sessions, prompts`);
|
|
101
106
|
return;
|
|
102
107
|
}
|
|
103
108
|
|
|
104
109
|
const ftsQuery = sanitizeFtsQuery(query);
|
|
105
110
|
if (!ftsQuery) {
|
|
106
|
-
|
|
111
|
+
fail(`[mem] No valid search terms in "${query}"`);
|
|
107
112
|
return;
|
|
108
113
|
}
|
|
109
114
|
|
|
115
|
+
// Warn if obs-only filters used with non-observation source
|
|
116
|
+
if (source && source !== 'observations' && (type || tier || minImportance)) {
|
|
117
|
+
const ignored = [type && '--type', tier && '--tier', minImportance && '--importance'].filter(Boolean);
|
|
118
|
+
process.stderr.write(`[mem] Note: ${ignored.join(', ')} only apply to observations, ignored for --source ${source}\n`);
|
|
119
|
+
}
|
|
120
|
+
|
|
110
121
|
// When --type/--tier/--importance (obs-only fields) is specified, implicitly restrict to observations
|
|
111
122
|
const effectiveSource = source || ((type || tier || minImportance) ? 'observations' : null);
|
|
112
123
|
|
|
@@ -211,7 +222,7 @@ function cmdSearch(db, args) {
|
|
|
211
222
|
sessParams.push(effectiveSource ? limit : limit, effectiveSource ? offset : 0);
|
|
212
223
|
try {
|
|
213
224
|
const sessRows = db.prepare(`
|
|
214
|
-
SELECT s.id, s.request, s.completed, s.project, s.created_at,
|
|
225
|
+
SELECT s.id, s.request, s.completed, s.project, s.created_at, s.created_at_epoch,
|
|
215
226
|
${SESS_BM25}
|
|
216
227
|
* (1.0 + EXP(-0.693 * (? - s.created_at_epoch) / ${DEFAULT_DECAY_HALF_LIFE_MS}.0))
|
|
217
228
|
* (CASE WHEN ? IS NOT NULL AND s.project = ? THEN 2.0 ELSE 1.0 END) as score
|
|
@@ -235,7 +246,7 @@ function cmdSearch(db, args) {
|
|
|
235
246
|
promptParams.push(effectiveSource ? limit : limit, effectiveSource ? offset : 0);
|
|
236
247
|
try {
|
|
237
248
|
const promptRows = db.prepare(`
|
|
238
|
-
SELECT p.id, p.prompt_text, p.content_session_id, p.created_at,
|
|
249
|
+
SELECT p.id, p.prompt_text, p.content_session_id, p.created_at, p.created_at_epoch,
|
|
239
250
|
bm25(user_prompts_fts, 1) as score
|
|
240
251
|
FROM user_prompts_fts
|
|
241
252
|
JOIN user_prompts p ON user_prompts_fts.rowid = p.id
|
|
@@ -285,22 +296,29 @@ function cmdSearch(db, args) {
|
|
|
285
296
|
}
|
|
286
297
|
// else 'relevance' keeps BM25 score order (already sorted)
|
|
287
298
|
|
|
288
|
-
//
|
|
289
|
-
const paged =
|
|
299
|
+
// Trim to limit with offset
|
|
300
|
+
const paged = results.slice(offset, offset + limit);
|
|
290
301
|
|
|
302
|
+
if (paged.length === 0) {
|
|
303
|
+
out(`[mem] No results for "${query}" at offset ${offset}`);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const showTime = sort === 'time';
|
|
291
308
|
out(`[mem] ${paged.length} result${paged.length !== 1 ? 's' : ''} for "${query}":`);
|
|
292
309
|
for (const r of paged) {
|
|
310
|
+
const timeStr = showTime && r.created_at_epoch ? ` (${relativeTime(r.created_at_epoch)})` : '';
|
|
293
311
|
if (r._source === 'session') {
|
|
294
312
|
const date = fmtDateShort(r.created_at);
|
|
295
|
-
out(`S#${r.id} 📋 ${date} ${truncate(r.request || r.completed || '(no summary)', 80)}`);
|
|
313
|
+
out(`S#${r.id} 📋 ${date}${timeStr} ${truncate(r.request || r.completed || '(no summary)', 80)}`);
|
|
296
314
|
} else if (r._source === 'prompt') {
|
|
297
315
|
const date = fmtDateShort(r.created_at);
|
|
298
|
-
out(`P#${r.id} 💬 ${date} ${truncate(r.prompt_text || '(empty)', 80)}`);
|
|
316
|
+
out(`P#${r.id} 💬 ${date}${timeStr} ${truncate(r.prompt_text || '(empty)', 80)}`);
|
|
299
317
|
} else {
|
|
300
318
|
const date = fmtDateShort(r.created_at);
|
|
301
319
|
const title = truncate(r.title || r.subtitle || '(untitled)', 80);
|
|
302
320
|
const supersededTag = r.superseded ? ' [SUPERSEDED]' : '';
|
|
303
|
-
out(`#${r.id} ${typeIcon(r.type)} ${date} ${title}${supersededTag}`);
|
|
321
|
+
out(`#${r.id} ${typeIcon(r.type)} ${date}${timeStr} ${title}${supersededTag}`);
|
|
304
322
|
if (r.lesson_learned) {
|
|
305
323
|
out(` -> ${truncate(r.lesson_learned, 80)}`);
|
|
306
324
|
}
|
|
@@ -333,7 +351,7 @@ function searchFts(db, ftsQuery, { type, project, limit, dateFrom, dateTo, minIm
|
|
|
333
351
|
|
|
334
352
|
// Scoring aligned with server.mjs: BM25 × type-decay × type-quality × project_boost × importance × access_bonus
|
|
335
353
|
const ftsRows = db.prepare(`
|
|
336
|
-
SELECT o.id, o.type, o.title, o.subtitle, o.created_at, o.lesson_learned,
|
|
354
|
+
SELECT o.id, o.type, o.title, o.subtitle, o.created_at, o.created_at_epoch, o.lesson_learned,
|
|
337
355
|
o.files_modified, o.importance,
|
|
338
356
|
${OBS_BM25}
|
|
339
357
|
* (1.0 + EXP(-0.693 * (? - MAX(o.created_at_epoch, COALESCE(o.last_accessed_at, o.created_at_epoch))) / ${TYPE_DECAY_CASE}))
|
|
@@ -407,7 +425,8 @@ function searchFts(db, ftsQuery, { type, project, limit, dateFrom, dateTo, minIm
|
|
|
407
425
|
|
|
408
426
|
function cmdRecent(db, args) {
|
|
409
427
|
const { positional, flags } = parseArgs(args);
|
|
410
|
-
const
|
|
428
|
+
const rawLimit = parseInt(positional[0], 10);
|
|
429
|
+
const limit = Math.max(1, Number.isFinite(rawLimit) ? rawLimit : 10);
|
|
411
430
|
const project = flags.project ? resolveProject(db, flags.project) : inferProject();
|
|
412
431
|
|
|
413
432
|
const params = [];
|
|
@@ -440,7 +459,7 @@ function cmdRecall(db, args) {
|
|
|
440
459
|
const { positional, flags } = parseArgs(args);
|
|
441
460
|
const file = positional.join(' ');
|
|
442
461
|
if (!file) {
|
|
443
|
-
|
|
462
|
+
fail('[mem] Usage: mem recall <file>');
|
|
444
463
|
return;
|
|
445
464
|
}
|
|
446
465
|
|
|
@@ -483,13 +502,13 @@ function cmdGet(db, args) {
|
|
|
483
502
|
const { positional, flags } = parseArgs(args);
|
|
484
503
|
const idStr = positional.join(',');
|
|
485
504
|
if (!idStr) {
|
|
486
|
-
|
|
505
|
+
fail('[mem] Usage: mem get <id1,id2,...> [--source obs|session|prompt] [--fields f1,f2,...]');
|
|
487
506
|
return;
|
|
488
507
|
}
|
|
489
508
|
|
|
490
509
|
const ids = idStr.split(',').map(s => parseInt(s.trim(), 10)).filter(n => !isNaN(n));
|
|
491
510
|
if (ids.length === 0) {
|
|
492
|
-
|
|
511
|
+
fail('[mem] No valid IDs provided');
|
|
493
512
|
return;
|
|
494
513
|
}
|
|
495
514
|
|
|
@@ -498,7 +517,7 @@ function cmdGet(db, args) {
|
|
|
498
517
|
|
|
499
518
|
if (source === 'session') {
|
|
500
519
|
const rows = db.prepare(`SELECT * FROM session_summaries WHERE id IN (${placeholders}) ORDER BY created_at_epoch ASC`).all(...ids);
|
|
501
|
-
if (rows.length === 0) {
|
|
520
|
+
if (rows.length === 0) { fail('[mem] No sessions found for given IDs'); return; }
|
|
502
521
|
const parts = [];
|
|
503
522
|
for (const r of rows) {
|
|
504
523
|
const lines = [`S#${r.id} ${fmtDateShort(r.created_at)}`];
|
|
@@ -516,7 +535,7 @@ function cmdGet(db, args) {
|
|
|
516
535
|
|
|
517
536
|
if (source === 'prompt') {
|
|
518
537
|
const rows = db.prepare(`SELECT * FROM user_prompts WHERE id IN (${placeholders}) ORDER BY created_at_epoch ASC`).all(...ids);
|
|
519
|
-
if (rows.length === 0) {
|
|
538
|
+
if (rows.length === 0) { fail('[mem] No prompts found for given IDs'); return; }
|
|
520
539
|
const parts = [];
|
|
521
540
|
for (const r of rows) {
|
|
522
541
|
const lines = [`P#${r.id} ${fmtDateShort(r.created_at)}`];
|
|
@@ -543,7 +562,7 @@ function cmdGet(db, args) {
|
|
|
543
562
|
`).all(...ids);
|
|
544
563
|
|
|
545
564
|
if (rows.length === 0) {
|
|
546
|
-
|
|
565
|
+
fail('[mem] No observations found for given IDs');
|
|
547
566
|
return;
|
|
548
567
|
}
|
|
549
568
|
|
|
@@ -627,7 +646,7 @@ function cmdTimeline(db, args) {
|
|
|
627
646
|
// Get anchor epoch
|
|
628
647
|
const anchorRow = db.prepare('SELECT created_at_epoch, project FROM observations WHERE id = ?').get(anchorId);
|
|
629
648
|
if (!anchorRow) {
|
|
630
|
-
|
|
649
|
+
fail(`[mem] Observation #${anchorId} not found`);
|
|
631
650
|
return;
|
|
632
651
|
}
|
|
633
652
|
|
|
@@ -672,20 +691,25 @@ function cmdSave(db, args) {
|
|
|
672
691
|
const { positional, flags } = parseArgs(args);
|
|
673
692
|
const text = positional.join(' ');
|
|
674
693
|
if (!text) {
|
|
675
|
-
|
|
694
|
+
fail('[mem] Usage: mem save "<text>" [--type T] [--title T] [--importance N] [--project P] [--files f1,f2]');
|
|
676
695
|
return;
|
|
677
696
|
}
|
|
678
697
|
|
|
679
698
|
const type = flags.type || 'discovery';
|
|
680
699
|
const validTypes = new Set(['decision', 'bugfix', 'feature', 'refactor', 'discovery', 'change']);
|
|
681
700
|
if (!validTypes.has(type)) {
|
|
682
|
-
|
|
701
|
+
fail(`[mem] Invalid type "${type}". Valid: ${[...validTypes].join(', ')}`);
|
|
683
702
|
return;
|
|
684
703
|
}
|
|
685
704
|
|
|
686
705
|
const rawTitle = flags.title || text.slice(0, 100);
|
|
687
706
|
// Explicit saves default to importance=2 (notable) — user chose to save
|
|
688
|
-
const
|
|
707
|
+
const rawImp = flags.importance !== undefined ? parseInt(flags.importance, 10) : 2;
|
|
708
|
+
if (flags.importance !== undefined && (isNaN(rawImp) || rawImp < 1 || rawImp > 3)) {
|
|
709
|
+
fail(`[mem] Invalid importance "${flags.importance}". Must be 1, 2, or 3.`);
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
const importance = rawImp;
|
|
689
713
|
const project = flags.project ? resolveProject(db, flags.project) : inferProject();
|
|
690
714
|
const saveFiles = flags.files ? flags.files.split(',').map(f => f.trim()).filter(Boolean) : [];
|
|
691
715
|
|
|
@@ -831,7 +855,8 @@ function cmdStats(db, args) {
|
|
|
831
855
|
const tdParams = tierSqlParams(tierCtx);
|
|
832
856
|
const tierDist = db.prepare(`
|
|
833
857
|
SELECT tier, COUNT(*) as c FROM (
|
|
834
|
-
SELECT ${TIER_CASE_SQL} as tier FROM observations
|
|
858
|
+
SELECT ${TIER_CASE_SQL} as tier FROM observations
|
|
859
|
+
WHERE COALESCE(compressed_into, 0) = 0 AND superseded_at IS NULL ${projectFilter}
|
|
835
860
|
) GROUP BY tier ORDER BY tier
|
|
836
861
|
`).all(...tdParams, ...baseParams);
|
|
837
862
|
const tierMap = Object.fromEntries(tierDist.map(r => [r.tier, r.c]));
|
|
@@ -924,7 +949,7 @@ function cmdBrowse(db, args) {
|
|
|
924
949
|
const project = flags.project ? resolveProject(db, flags.project) : inferProject();
|
|
925
950
|
const tierFilter = flags.tier || null;
|
|
926
951
|
if (tierFilter && !['working', 'active', 'archive'].includes(tierFilter)) {
|
|
927
|
-
|
|
952
|
+
fail(`[mem] Invalid tier: "${tierFilter}". Use: working, active, or archive`);
|
|
928
953
|
return;
|
|
929
954
|
}
|
|
930
955
|
const limit = Math.max(1, parseInt(flags.limit, 10) || (tierFilter ? 20 : 5));
|
|
@@ -1007,13 +1032,13 @@ function cmdDelete(db, args) {
|
|
|
1007
1032
|
const { positional, flags } = parseArgs(args);
|
|
1008
1033
|
const idStr = positional.join(',');
|
|
1009
1034
|
if (!idStr) {
|
|
1010
|
-
|
|
1035
|
+
fail('[mem] Usage: mem delete <id1,id2,...> [--confirm]');
|
|
1011
1036
|
return;
|
|
1012
1037
|
}
|
|
1013
1038
|
|
|
1014
1039
|
const ids = idStr.split(',').map(s => parseInt(s.trim(), 10)).filter(n => !isNaN(n));
|
|
1015
1040
|
if (ids.length === 0) {
|
|
1016
|
-
|
|
1041
|
+
fail('[mem] No valid IDs provided');
|
|
1017
1042
|
return;
|
|
1018
1043
|
}
|
|
1019
1044
|
|
|
@@ -1022,7 +1047,7 @@ function cmdDelete(db, args) {
|
|
|
1022
1047
|
const rows = db.prepare(`SELECT id, type, title, project FROM observations WHERE id IN (${placeholders})`).all(...ids);
|
|
1023
1048
|
|
|
1024
1049
|
if (rows.length === 0) {
|
|
1025
|
-
|
|
1050
|
+
fail('[mem] No observations found for given IDs');
|
|
1026
1051
|
return;
|
|
1027
1052
|
}
|
|
1028
1053
|
|
|
@@ -1066,24 +1091,24 @@ function cmdUpdate(db, args) {
|
|
|
1066
1091
|
const { positional, flags } = parseArgs(args);
|
|
1067
1092
|
const id = parseInt(positional[0], 10);
|
|
1068
1093
|
if (!id || isNaN(id)) {
|
|
1069
|
-
|
|
1094
|
+
fail('[mem] Usage: mem update <id> [--title T] [--type T] [--importance N] [--lesson T] [--narrative T] [--concepts T]');
|
|
1070
1095
|
return;
|
|
1071
1096
|
}
|
|
1072
1097
|
|
|
1073
1098
|
const obs = db.prepare('SELECT id, title FROM observations WHERE id = ?').get(id);
|
|
1074
1099
|
if (!obs) {
|
|
1075
|
-
|
|
1100
|
+
fail(`[mem] Observation #${id} not found`);
|
|
1076
1101
|
return;
|
|
1077
1102
|
}
|
|
1078
1103
|
|
|
1079
1104
|
const updates = [];
|
|
1080
1105
|
const params = [];
|
|
1081
|
-
if (flags.title) { updates.push('title = ?'); params.push(scrubSecrets(flags.title)); }
|
|
1082
|
-
if (flags.narrative) { updates.push('narrative = ?'); params.push(scrubSecrets(flags.narrative)); }
|
|
1106
|
+
if (flags.title !== undefined) { updates.push('title = ?'); params.push(scrubSecrets(flags.title)); }
|
|
1107
|
+
if (flags.narrative !== undefined) { updates.push('narrative = ?'); params.push(scrubSecrets(flags.narrative)); }
|
|
1083
1108
|
if (flags.type) {
|
|
1084
1109
|
const validTypes = new Set(['decision', 'bugfix', 'feature', 'refactor', 'discovery', 'change']);
|
|
1085
1110
|
if (!validTypes.has(flags.type)) {
|
|
1086
|
-
|
|
1111
|
+
fail(`[mem] Invalid type "${flags.type}". Valid: ${[...validTypes].join(', ')}`);
|
|
1087
1112
|
return;
|
|
1088
1113
|
}
|
|
1089
1114
|
updates.push('type = ?'); params.push(flags.type);
|
|
@@ -1091,16 +1116,16 @@ function cmdUpdate(db, args) {
|
|
|
1091
1116
|
if (flags.importance) {
|
|
1092
1117
|
const imp = parseInt(flags.importance, 10);
|
|
1093
1118
|
if (isNaN(imp) || imp < 1 || imp > 3) {
|
|
1094
|
-
|
|
1119
|
+
fail(`[mem] Invalid importance "${flags.importance}". Must be 1, 2, or 3.`);
|
|
1095
1120
|
return;
|
|
1096
1121
|
}
|
|
1097
1122
|
updates.push('importance = ?'); params.push(imp);
|
|
1098
1123
|
}
|
|
1099
|
-
if (flags.lesson || flags['lesson-learned']) { updates.push('lesson_learned = ?'); params.push(scrubSecrets(flags.lesson
|
|
1100
|
-
if (flags.concepts) { updates.push('concepts = ?'); params.push(flags.concepts); }
|
|
1124
|
+
if (flags.lesson !== undefined || flags['lesson-learned'] !== undefined) { updates.push('lesson_learned = ?'); params.push(scrubSecrets(flags.lesson ?? flags['lesson-learned'] ?? '')); }
|
|
1125
|
+
if (flags.concepts !== undefined) { updates.push('concepts = ?'); params.push(flags.concepts); }
|
|
1101
1126
|
|
|
1102
1127
|
if (updates.length === 0) {
|
|
1103
|
-
|
|
1128
|
+
fail('[mem] No fields to update. Use --title, --type, --importance, --lesson/--lesson-learned, --narrative, --concepts');
|
|
1104
1129
|
return;
|
|
1105
1130
|
}
|
|
1106
1131
|
|
|
@@ -1150,12 +1175,12 @@ function cmdExport(db, args) {
|
|
|
1150
1175
|
if (flags.type) { wheres.push('type = ?'); params.push(flags.type); }
|
|
1151
1176
|
if (flags.from) {
|
|
1152
1177
|
const epoch = new Date(flags.from).getTime();
|
|
1153
|
-
if (isNaN(epoch)) {
|
|
1178
|
+
if (isNaN(epoch)) { fail(`[mem] Invalid --from date: "${flags.from}". Use YYYY-MM-DD or ISO 8601.`); return; }
|
|
1154
1179
|
wheres.push('created_at_epoch >= ?'); params.push(epoch);
|
|
1155
1180
|
}
|
|
1156
1181
|
if (flags.to) {
|
|
1157
1182
|
let epoch = new Date(flags.to).getTime();
|
|
1158
|
-
if (isNaN(epoch)) {
|
|
1183
|
+
if (isNaN(epoch)) { fail(`[mem] Invalid --to date: "${flags.to}". Use YYYY-MM-DD or ISO 8601.`); return; }
|
|
1159
1184
|
if (/^\d{4}-\d{2}-\d{2}$/.test(flags.to)) epoch += 86400000 - 1;
|
|
1160
1185
|
wheres.push('created_at_epoch <= ?'); params.push(epoch);
|
|
1161
1186
|
}
|
|
@@ -1163,7 +1188,7 @@ function cmdExport(db, args) {
|
|
|
1163
1188
|
const limit = Math.min(Math.max(1, parseInt(flags.limit, 10) || 200), 1000);
|
|
1164
1189
|
const format = flags.format || 'json';
|
|
1165
1190
|
if (!['json', 'jsonl'].includes(format)) {
|
|
1166
|
-
|
|
1191
|
+
fail(`[mem] Invalid format "${format}". Use: json or jsonl`);
|
|
1167
1192
|
return;
|
|
1168
1193
|
}
|
|
1169
1194
|
|
|
@@ -1292,7 +1317,7 @@ function cmdMaintain(db, args) {
|
|
|
1292
1317
|
const { positional, flags } = parseArgs(args);
|
|
1293
1318
|
const action = positional[0];
|
|
1294
1319
|
if (!action || !['scan', 'execute'].includes(action)) {
|
|
1295
|
-
|
|
1320
|
+
fail('[mem] Usage: mem maintain <scan|execute> [--ops cleanup,decay,boost,dedup,purge_stale,rebuild_vectors] [--project P] [--retain-days N] [--merge-ids keepId:removeId,...]');
|
|
1296
1321
|
return;
|
|
1297
1322
|
}
|
|
1298
1323
|
|
|
@@ -1353,7 +1378,7 @@ function cmdMaintain(db, args) {
|
|
|
1353
1378
|
out(` Stale (>30d, imp=1, no access): ${stats.stale}`);
|
|
1354
1379
|
out(` Broken (no title/narrative): ${stats.broken}`);
|
|
1355
1380
|
out(` Boostable (accessed>3, imp<3): ${stats.boostable}`);
|
|
1356
|
-
out(` Pending purge: ${pendingPurge.count}`);
|
|
1381
|
+
out(` Pending purge: ${pendingPurge.count} (compressed originals awaiting cleanup)`);
|
|
1357
1382
|
if (duplicates.length > 0) {
|
|
1358
1383
|
const AUTO_MERGE_THRESHOLD = 0.85;
|
|
1359
1384
|
const autoMergeable = duplicates.filter(d => parseFloat(d.similarity) >= AUTO_MERGE_THRESHOLD);
|
|
@@ -1390,7 +1415,7 @@ function cmdMaintain(db, args) {
|
|
|
1390
1415
|
const ops = opsStr.split(',').map(s => s.trim());
|
|
1391
1416
|
const invalidOps = ops.filter(op => !VALID_OPS.includes(op));
|
|
1392
1417
|
if (invalidOps.length > 0) {
|
|
1393
|
-
|
|
1418
|
+
fail(`[mem] Unknown operation(s): ${invalidOps.join(', ')}. Valid: ${VALID_OPS.join(', ')}`);
|
|
1394
1419
|
return;
|
|
1395
1420
|
}
|
|
1396
1421
|
const staleAge = Date.now() - STALE_AGE_MS;
|
|
@@ -1527,7 +1552,7 @@ function cmdFtsCheck(db, args) {
|
|
|
1527
1552
|
const { positional } = parseArgs(args);
|
|
1528
1553
|
const action = positional[0];
|
|
1529
1554
|
if (!action || !['check', 'rebuild'].includes(action)) {
|
|
1530
|
-
|
|
1555
|
+
fail('[mem] Usage: mem fts-check <check|rebuild>');
|
|
1531
1556
|
return;
|
|
1532
1557
|
}
|
|
1533
1558
|
|
|
@@ -1558,7 +1583,7 @@ function cmdRegistry(_memDb, args) {
|
|
|
1558
1583
|
const { positional, flags } = parseArgs(args);
|
|
1559
1584
|
const action = positional[0];
|
|
1560
1585
|
if (!action || !['list', 'stats', 'search', 'import', 'remove', 'reindex'].includes(action)) {
|
|
1561
|
-
|
|
1586
|
+
fail('[mem] Usage: mem registry <list|stats|search|import|remove|reindex> [--type skill|agent] [--query Q] [--name N] [--resource-type T]');
|
|
1562
1587
|
return;
|
|
1563
1588
|
}
|
|
1564
1589
|
|
|
@@ -1574,7 +1599,7 @@ function cmdRegistry(_memDb, args) {
|
|
|
1574
1599
|
try {
|
|
1575
1600
|
if (action === 'search') {
|
|
1576
1601
|
const query = flags.query || positional.slice(1).join(' ');
|
|
1577
|
-
if (!query) {
|
|
1602
|
+
if (!query) { fail('[mem] Usage: mem registry search <query> [--type skill|agent] [--category C] [--quality Q]'); return; }
|
|
1578
1603
|
let results = searchResources(rdb, query, {
|
|
1579
1604
|
type: flags.type || undefined,
|
|
1580
1605
|
limit: (flags.category || flags.quality) ? 20 : 10,
|
|
@@ -1646,7 +1671,7 @@ function cmdRegistry(_memDb, args) {
|
|
|
1646
1671
|
if (action === 'import') {
|
|
1647
1672
|
const name = flags.name;
|
|
1648
1673
|
const resourceType = flags['resource-type'];
|
|
1649
|
-
if (!name || !resourceType) {
|
|
1674
|
+
if (!name || !resourceType) { fail('[mem] Usage: mem registry import --name N --resource-type skill|agent [--invocation-name I] [--capability-summary S]'); return; }
|
|
1650
1675
|
const fields = { name, type: resourceType, status: 'active', source: flags.source || 'user' };
|
|
1651
1676
|
for (const f of ['repo-url', 'local-path', 'invocation-name', 'intent-tags', 'domain-tags', 'trigger-patterns', 'capability-summary', 'keywords', 'tech-stack', 'use-cases']) {
|
|
1652
1677
|
const camel = f.replace(/-([a-z])/g, (_, c) => '_' + c);
|
|
@@ -1667,7 +1692,7 @@ function cmdRegistry(_memDb, args) {
|
|
|
1667
1692
|
if (action === 'remove') {
|
|
1668
1693
|
const name = flags.name;
|
|
1669
1694
|
const resourceType = flags['resource-type'];
|
|
1670
|
-
if (!name || !resourceType) {
|
|
1695
|
+
if (!name || !resourceType) { fail('[mem] Usage: mem registry remove --name N --resource-type skill|agent'); return; }
|
|
1671
1696
|
const result = rdb.prepare('DELETE FROM resources WHERE type = ? AND name = ?').run(resourceType, name);
|
|
1672
1697
|
out(result.changes > 0 ? `[mem] Removed: ${resourceType}:${name}` : '[mem] Not found.');
|
|
1673
1698
|
return;
|
package/package.json
CHANGED
package/scripts/launch.mjs
CHANGED
|
@@ -20,6 +20,28 @@ if (!existsSync(join(ROOT, 'node_modules', 'better-sqlite3'))) {
|
|
|
20
20
|
process.stderr.write('[claude-mem-lite] Dependencies installed\n');
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
// Verify MCP SDK is importable (exports mapping intact).
|
|
24
|
+
// Incomplete installs can leave the directory present but package.json missing,
|
|
25
|
+
// causing Node.js to fail resolving subpath exports like /server/mcp.js.
|
|
26
|
+
try {
|
|
27
|
+
await import('@modelcontextprotocol/sdk/server/mcp.js');
|
|
28
|
+
} catch (firstErr) {
|
|
29
|
+
process.stderr.write(`[claude-mem-lite] MCP SDK broken (${firstErr.code || firstErr.message}) — reinstalling...\n`);
|
|
30
|
+
try {
|
|
31
|
+
execSync('npm install @modelcontextprotocol/sdk --force --omit=dev --no-audit --no-fund', {
|
|
32
|
+
cwd: ROOT,
|
|
33
|
+
stdio: ['ignore', 'pipe', 'inherit'],
|
|
34
|
+
timeout: 60_000,
|
|
35
|
+
});
|
|
36
|
+
// Verify the reinstall actually fixed it
|
|
37
|
+
await import('@modelcontextprotocol/sdk/server/mcp.js');
|
|
38
|
+
process.stderr.write('[claude-mem-lite] MCP SDK repaired\n');
|
|
39
|
+
} catch (e) {
|
|
40
|
+
process.stderr.write(`[claude-mem-lite] MCP SDK repair failed: ${e.message}\n`);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
23
45
|
// Dev mode: prefer ~/.claude-mem-lite/server.mjs (symlinked to source) over
|
|
24
46
|
// CLAUDE_PLUGIN_ROOT (potentially stale plugin cache). This ensures the MCP
|
|
25
47
|
// server always runs the latest code when installed with `install --dev`.
|
package/scripts/setup.sh
CHANGED
|
@@ -122,7 +122,11 @@ if [[ -n "${CLAUDE_PLUGIN_ROOT:-}" ]]; then
|
|
|
122
122
|
CACHE_DIR="$HOME/.claude/plugins/cache/sdsrss/claude-mem-lite"
|
|
123
123
|
if [[ -d "$CACHE_DIR" ]]; then
|
|
124
124
|
# List version dirs sorted by semver descending, skip top 3
|
|
125
|
-
|
|
125
|
+
# Use while-read instead of mapfile for bash 3.2 (macOS) compatibility
|
|
126
|
+
OLD_VERS=()
|
|
127
|
+
while IFS= read -r ver; do
|
|
128
|
+
[[ -n "$ver" ]] && OLD_VERS+=("$ver")
|
|
129
|
+
done < <(ls -1 "$CACHE_DIR" | grep -E '^[0-9]+\.' | sort -t. -k1,1nr -k2,2nr -k3,3nr | tail -n +4)
|
|
126
130
|
if [[ ${#OLD_VERS[@]} -gt 0 ]]; then
|
|
127
131
|
for ver in "${OLD_VERS[@]}"; do
|
|
128
132
|
rm -rf "${CACHE_DIR:?}/$ver" 2>/dev/null || true
|
package/server.mjs
CHANGED
|
@@ -1064,7 +1064,7 @@ server.registerTool(
|
|
|
1064
1064
|
const tierDist = db.prepare(`
|
|
1065
1065
|
SELECT tier, COUNT(*) as c FROM (
|
|
1066
1066
|
SELECT ${TIER_CASE_SQL} as tier FROM observations
|
|
1067
|
-
WHERE
|
|
1067
|
+
WHERE COALESCE(compressed_into, 0) = 0 AND superseded_at IS NULL ${projectFilter}
|
|
1068
1068
|
) GROUP BY tier ORDER BY tier
|
|
1069
1069
|
`).all(...tdParams, ...baseParams);
|
|
1070
1070
|
const tierMap = Object.fromEntries(tierDist.map(r => [r.tier, r.c]));
|