claude-mem-lite 2.9.2 → 2.9.4
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/README.md +9 -2
- package/README.zh-CN.md +9 -2
- package/cli.mjs +3 -1
- package/dispatch.mjs +7 -4
- package/hook-update.mjs +123 -43
- package/hook.mjs +21 -1
- package/install.mjs +180 -75
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -121,7 +121,7 @@ The original sends **everything to the LLM and hopes it filters well**. claude-m
|
|
|
121
121
|
/plugin install claude-mem-lite
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
-
|
|
124
|
+
Plugin mode manages its own hooks/runtime. On session start it only **checks and reports** new claude-mem-lite versions; it does **not** self-overwrite plugin files in place. Update plugin-mode installs through Claude's plugin workflow.
|
|
125
125
|
|
|
126
126
|
### Method 2: npx (one-liner)
|
|
127
127
|
|
|
@@ -375,6 +375,8 @@ node install.mjs uninstall # Remove (keep data)
|
|
|
375
375
|
node install.mjs uninstall --purge # Remove and delete all data
|
|
376
376
|
node install.mjs status # Show current status
|
|
377
377
|
node install.mjs doctor # Diagnose issues
|
|
378
|
+
node install.mjs cleanup-hooks # Remove only stale claude-mem-lite hooks from settings.json
|
|
379
|
+
node install.mjs update # Force-check for updates and install them (direct install / npx mode)
|
|
378
380
|
|
|
379
381
|
# npx install:
|
|
380
382
|
npx claude-mem-lite # Install / reinstall
|
|
@@ -382,13 +384,18 @@ npx claude-mem-lite uninstall # Remove (keep data)
|
|
|
382
384
|
npx claude-mem-lite doctor # Diagnose issues
|
|
383
385
|
```
|
|
384
386
|
|
|
387
|
+
Notes:
|
|
388
|
+
- Plugin mode only reports available updates; it does not self-update plugin files.
|
|
389
|
+
- Direct install / npx mode keeps auto-update enabled and uses staged replacement with rollback on install failure.
|
|
390
|
+
- If you disabled the plugin but still have old mem hooks in `~/.claude/settings.json`, run `node install.mjs cleanup-hooks`.
|
|
391
|
+
|
|
385
392
|
### doctor
|
|
386
393
|
|
|
387
394
|
Checks Node.js version, dependencies, server/hook files, database integrity, FTS5 indexes, and stale processes.
|
|
388
395
|
|
|
389
396
|
### status
|
|
390
397
|
|
|
391
|
-
Shows MCP registration, hook configuration, and database stats (observation/session counts).
|
|
398
|
+
Shows MCP registration, hook configuration, plugin disabled state, and database stats (observation/session counts).
|
|
392
399
|
|
|
393
400
|
## Uninstall
|
|
394
401
|
|
package/README.zh-CN.md
CHANGED
|
@@ -121,7 +121,7 @@
|
|
|
121
121
|
/plugin install claude-mem-lite
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
-
|
|
124
|
+
插件模式会管理自己的运行时与钩子。SessionStart 时它现在只会**检查并提示**新版本,不会直接覆盖插件目录中的文件。插件模式请通过 Claude 的插件更新流程完成升级。
|
|
125
125
|
|
|
126
126
|
### 方式二:npx(一行命令)
|
|
127
127
|
|
|
@@ -375,6 +375,8 @@ node install.mjs uninstall # 移除(保留数据)
|
|
|
375
375
|
node install.mjs uninstall --purge # 移除并删除所有数据
|
|
376
376
|
node install.mjs status # 显示当前状态
|
|
377
377
|
node install.mjs doctor # 诊断问题
|
|
378
|
+
node install.mjs cleanup-hooks # 只清理 settings.json 中残留的 claude-mem-lite hooks
|
|
379
|
+
node install.mjs update # 强制检查并安装更新(direct install / npx 模式)
|
|
378
380
|
|
|
379
381
|
# npx 安装:
|
|
380
382
|
npx claude-mem-lite # 安装 / 重新安装
|
|
@@ -382,13 +384,18 @@ npx claude-mem-lite uninstall # 移除(保留数据)
|
|
|
382
384
|
npx claude-mem-lite doctor # 诊断问题
|
|
383
385
|
```
|
|
384
386
|
|
|
387
|
+
说明:
|
|
388
|
+
- 插件模式只提示可用更新,不会自更新插件文件。
|
|
389
|
+
- direct install / npx 模式保留自动更新,并使用 staged replacement;若依赖安装失败会回滚。
|
|
390
|
+
- 如果你禁用了插件,但 `~/.claude/settings.json` 里还有旧的 mem hooks,可运行 `node install.mjs cleanup-hooks`。
|
|
391
|
+
|
|
385
392
|
### doctor
|
|
386
393
|
|
|
387
394
|
检查 Node.js 版本、依赖、服务器/钩子文件、数据库完整性、FTS5 索引和残留进程。
|
|
388
395
|
|
|
389
396
|
### status
|
|
390
397
|
|
|
391
|
-
显示 MCP
|
|
398
|
+
显示 MCP 注册状态、钩子配置、插件禁用状态和数据库统计(观察/会话数量)。
|
|
392
399
|
|
|
393
400
|
## 卸载
|
|
394
401
|
|
package/cli.mjs
CHANGED
package/dispatch.mjs
CHANGED
|
@@ -840,8 +840,11 @@ function passesConfidenceGate(results, signals) {
|
|
|
840
840
|
const top1 = Math.abs(results[0].composite_score ?? results[0].relevance ?? 0);
|
|
841
841
|
const top2 = Math.abs(results[1].composite_score ?? results[1].relevance ?? 0);
|
|
842
842
|
// After keyword re-ranking, #1 may have lower raw BM25 than #2.
|
|
843
|
-
//
|
|
844
|
-
|
|
843
|
+
// Only skip gap check if #1 was actually promoted by a keyword match
|
|
844
|
+
// (not just any rawKeywords present with incidentally inverted scores).
|
|
845
|
+
const top1Tags = (results[0].intent_tags || '').toLowerCase();
|
|
846
|
+
const top1MatchesKw = signals?.rawKeywords?.some(kw => top1Tags.includes(kw));
|
|
847
|
+
const wasReRanked = top1MatchesKw && top1 < top2;
|
|
845
848
|
if (!wasReRanked && top1 > 0) {
|
|
846
849
|
const gapRatio = (top1 - top2) / top1;
|
|
847
850
|
if (gapRatio < 0.2) {
|
|
@@ -1007,8 +1010,8 @@ function decideTier(resource, signals) {
|
|
|
1007
1010
|
else signalBoost += 0.1;
|
|
1008
1011
|
}
|
|
1009
1012
|
if (signals?.rawKeywords?.length > 0) {
|
|
1010
|
-
const
|
|
1011
|
-
if (signals.rawKeywords.some(kw =>
|
|
1013
|
+
const tagArr = (resource.intent_tags || '').toLowerCase().split(/[\s,]+/);
|
|
1014
|
+
if (signals.rawKeywords.some(kw => tagArr.includes(kw))) signalBoost += 0.2;
|
|
1012
1015
|
}
|
|
1013
1016
|
|
|
1014
1017
|
const confidence = Math.min(1.0, normalized + patternBoost * 0.3 + signalBoost);
|
package/hook-update.mjs
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
// Skips in dev mode (symlinked installs). Silent on network failure.
|
|
4
4
|
|
|
5
5
|
import { execSync } from 'node:child_process';
|
|
6
|
-
import { readFileSync, writeFileSync, copyFileSync, readdirSync, existsSync, lstatSync, mkdirSync, rmSync } from 'node:fs';
|
|
7
|
-
import { join } from 'node:path';
|
|
6
|
+
import { readFileSync, writeFileSync, copyFileSync, readdirSync, existsSync, lstatSync, mkdirSync, rmSync, renameSync } from 'node:fs';
|
|
7
|
+
import { join, dirname } from 'node:path';
|
|
8
8
|
import { tmpdir } from 'node:os';
|
|
9
9
|
import { DB_DIR } from './schema.mjs';
|
|
10
10
|
import { debugCatch, debugLog } from './utils.mjs';
|
|
@@ -16,17 +16,29 @@ const STATE_FILE = join(INSTALL_DIR, 'runtime', 'update-state.json');
|
|
|
16
16
|
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
17
17
|
const FETCH_TIMEOUT_MS = 3000; // 3s network timeout
|
|
18
18
|
const RATE_LIMIT_INTERVAL_MS = 6 * 60 * 60 * 1000; // 6h if rate-limited
|
|
19
|
+
const NPM_INSTALL_CMD = 'npm install --omit=dev --no-audit --no-fund';
|
|
19
20
|
|
|
20
21
|
// ── Main Entry ─────────────────────────────────────────────
|
|
21
|
-
export async function checkForUpdate() {
|
|
22
|
+
export async function checkForUpdate(options = {}) {
|
|
22
23
|
try {
|
|
24
|
+
const pluginMode = isPluginMode();
|
|
25
|
+
const force = Boolean(options.force);
|
|
26
|
+
const allowInstall = options.allowInstall ?? !pluginMode;
|
|
27
|
+
|
|
23
28
|
if (isDevMode() || process.env.CLAUDE_MEM_SKIP_UPDATE) return null;
|
|
24
29
|
|
|
25
30
|
const state = readState();
|
|
26
|
-
if (!shouldCheck(state)) {
|
|
31
|
+
if (!force && !shouldCheck(state)) {
|
|
27
32
|
// Return cached update info if previously detected
|
|
28
33
|
if (state.updateAvailable && state.latestVersion) {
|
|
29
|
-
return {
|
|
34
|
+
return {
|
|
35
|
+
updateAvailable: true,
|
|
36
|
+
updated: false,
|
|
37
|
+
from: state.installedVersion,
|
|
38
|
+
to: state.latestVersion,
|
|
39
|
+
installDeferred: pluginMode || !allowInstall,
|
|
40
|
+
pluginMode,
|
|
41
|
+
};
|
|
30
42
|
}
|
|
31
43
|
return null;
|
|
32
44
|
}
|
|
@@ -42,7 +54,8 @@ export async function checkForUpdate() {
|
|
|
42
54
|
|
|
43
55
|
if (hasUpdate) {
|
|
44
56
|
debugLog('DEBUG', 'hook-update', `Update available: ${currentVersion} → ${latest.version}`);
|
|
45
|
-
const
|
|
57
|
+
const canInstall = !pluginMode && Boolean(allowInstall);
|
|
58
|
+
const success = canInstall ? await downloadAndInstall(latest.tarballUrl) : false;
|
|
46
59
|
const newState = {
|
|
47
60
|
lastCheck: new Date().toISOString(),
|
|
48
61
|
installedVersion: success ? latest.version : currentVersion,
|
|
@@ -58,6 +71,8 @@ export async function checkForUpdate() {
|
|
|
58
71
|
updated: success,
|
|
59
72
|
from: currentVersion,
|
|
60
73
|
to: latest.version,
|
|
74
|
+
installDeferred: !canInstall,
|
|
75
|
+
pluginMode,
|
|
61
76
|
};
|
|
62
77
|
}
|
|
63
78
|
|
|
@@ -76,6 +91,10 @@ export async function checkForUpdate() {
|
|
|
76
91
|
}
|
|
77
92
|
}
|
|
78
93
|
|
|
94
|
+
function isPluginMode() {
|
|
95
|
+
return Boolean(process.env.CLAUDE_PLUGIN_ROOT);
|
|
96
|
+
}
|
|
97
|
+
|
|
79
98
|
// ── Dev Mode Detection ─────────────────────────────────────
|
|
80
99
|
function isDevMode() {
|
|
81
100
|
try {
|
|
@@ -172,12 +191,13 @@ const SOURCE_FILES = [
|
|
|
172
191
|
'server.mjs', 'server-internals.mjs', 'tool-schemas.mjs',
|
|
173
192
|
'hook.mjs', 'hook-shared.mjs', 'hook-llm.mjs', 'hook-memory.mjs',
|
|
174
193
|
'hook-semaphore.mjs', 'hook-episode.mjs', 'hook-context.mjs', 'hook-handoff.mjs', 'hook-update.mjs',
|
|
175
|
-
'haiku-client.mjs', 'utils.mjs', 'schema.mjs', 'package.json', 'skill.md',
|
|
194
|
+
'haiku-client.mjs', 'utils.mjs', 'schema.mjs', 'package.json', 'package-lock.json', 'skill.md',
|
|
176
195
|
'registry.mjs', 'registry-scanner.mjs', 'registry-indexer.mjs',
|
|
177
196
|
'registry-retriever.mjs', 'resource-discovery.mjs',
|
|
178
|
-
'dispatch.mjs', 'dispatch-inject.mjs', 'dispatch-feedback.mjs', 'dispatch-workflow.mjs',
|
|
197
|
+
'dispatch.mjs', 'dispatch-inject.mjs', 'dispatch-feedback.mjs', 'dispatch-patterns.mjs', 'dispatch-workflow.mjs',
|
|
179
198
|
'install.mjs',
|
|
180
199
|
];
|
|
200
|
+
const SWITCHABLE_PATHS = [...SOURCE_FILES, 'scripts', 'registry', 'node_modules'];
|
|
181
201
|
|
|
182
202
|
// ── Download & Install ─────────────────────────────────────
|
|
183
203
|
// Direct file copy instead of running old install.mjs (avoids symlink overwrite in dev)
|
|
@@ -197,53 +217,113 @@ async function downloadAndInstall(tarballUrl) {
|
|
|
197
217
|
{ timeout: 30000, stdio: 'pipe' }
|
|
198
218
|
);
|
|
199
219
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
220
|
+
return installExtractedRelease(tmpDir);
|
|
221
|
+
} catch (err) {
|
|
222
|
+
debugCatch(err, 'downloadAndInstall');
|
|
223
|
+
return false;
|
|
224
|
+
} finally {
|
|
225
|
+
try { rmSync(tmpDir, { recursive: true, force: true }); } catch {}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function installExtractedRelease(sourceDir, targetDir = INSTALL_DIR) {
|
|
230
|
+
const ts = `${Date.now()}-${process.pid}`;
|
|
231
|
+
const stagingDir = join(targetDir, `.update-staging-${ts}`);
|
|
232
|
+
const backupDir = join(targetDir, `.update-backup-${ts}`);
|
|
233
|
+
const backedUp = [];
|
|
234
|
+
const installed = [];
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
mkdirSync(stagingDir, { recursive: true });
|
|
238
|
+
mkdirSync(backupDir, { recursive: true });
|
|
239
|
+
|
|
240
|
+
copyReleaseIntoStaging(sourceDir, stagingDir);
|
|
241
|
+
execSync(NPM_INSTALL_CMD, {
|
|
242
|
+
cwd: stagingDir,
|
|
243
|
+
timeout: 60000,
|
|
244
|
+
stdio: 'pipe',
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
for (const relPath of SWITCHABLE_PATHS) {
|
|
248
|
+
const stagedPath = join(stagingDir, relPath);
|
|
249
|
+
if (!existsSync(stagedPath)) continue;
|
|
250
|
+
|
|
251
|
+
const targetPath = join(targetDir, relPath);
|
|
252
|
+
const backupPath = join(backupDir, relPath);
|
|
211
253
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
copyFileSync(join(srcScripts, f), join(destScripts, f));
|
|
254
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
255
|
+
mkdirSync(dirname(backupPath), { recursive: true });
|
|
256
|
+
|
|
257
|
+
if (existsSync(targetPath)) {
|
|
258
|
+
renameSync(targetPath, backupPath);
|
|
259
|
+
backedUp.push(relPath);
|
|
219
260
|
}
|
|
261
|
+
|
|
262
|
+
renameSync(stagedPath, targetPath);
|
|
263
|
+
installed.push(relPath);
|
|
220
264
|
}
|
|
221
265
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
266
|
+
rmSync(stagingDir, { recursive: true, force: true });
|
|
267
|
+
rmSync(backupDir, { recursive: true, force: true });
|
|
268
|
+
debugLog('DEBUG', 'hook-update', `Auto-update: switched ${installed.length} paths`);
|
|
269
|
+
return true;
|
|
270
|
+
} catch (err) {
|
|
271
|
+
debugCatch(err, 'installExtractedRelease');
|
|
272
|
+
|
|
273
|
+
for (const relPath of installed.reverse()) {
|
|
274
|
+
try { rmSync(join(targetDir, relPath), { recursive: true, force: true }); } catch {}
|
|
275
|
+
}
|
|
276
|
+
for (const relPath of backedUp.reverse()) {
|
|
277
|
+
const backupPath = join(backupDir, relPath);
|
|
278
|
+
const targetPath = join(targetDir, relPath);
|
|
225
279
|
try {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
debugCatch(err, 'downloadAndInstall-npm');
|
|
233
|
-
// Non-fatal: old node_modules may still work
|
|
280
|
+
if (existsSync(backupPath)) {
|
|
281
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
282
|
+
renameSync(backupPath, targetPath);
|
|
283
|
+
}
|
|
284
|
+
} catch (restoreErr) {
|
|
285
|
+
debugCatch(restoreErr, `installExtractedRelease-restore-${relPath}`);
|
|
234
286
|
}
|
|
235
287
|
}
|
|
236
288
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
} catch (err) {
|
|
240
|
-
debugCatch(err, 'downloadAndInstall');
|
|
289
|
+
try { rmSync(stagingDir, { recursive: true, force: true }); } catch {}
|
|
290
|
+
try { rmSync(backupDir, { recursive: true, force: true }); } catch {}
|
|
241
291
|
return false;
|
|
242
|
-
} finally {
|
|
243
|
-
try { rmSync(tmpDir, { recursive: true, force: true }); } catch {}
|
|
244
292
|
}
|
|
245
293
|
}
|
|
246
294
|
|
|
295
|
+
function copyReleaseIntoStaging(sourceDir, stagingDir) {
|
|
296
|
+
let copied = 0;
|
|
297
|
+
|
|
298
|
+
for (const f of SOURCE_FILES) {
|
|
299
|
+
const src = join(sourceDir, f);
|
|
300
|
+
const dest = join(stagingDir, f);
|
|
301
|
+
if (!existsSync(src)) continue;
|
|
302
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
303
|
+
copyFileSync(src, dest);
|
|
304
|
+
copied++;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
for (const dirName of ['scripts', 'registry']) {
|
|
308
|
+
const srcDir = join(sourceDir, dirName);
|
|
309
|
+
const destDir = join(stagingDir, dirName);
|
|
310
|
+
if (!existsSync(srcDir)) continue;
|
|
311
|
+
mkdirSync(destDir, { recursive: true });
|
|
312
|
+
for (const entry of readdirSync(srcDir)) {
|
|
313
|
+
copyFileSync(join(srcDir, entry), join(destDir, entry));
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const stagedScripts = join(stagingDir, 'scripts');
|
|
318
|
+
if (existsSync(stagedScripts)) {
|
|
319
|
+
for (const sf of readdirSync(stagedScripts).filter(n => n.endsWith('.sh'))) {
|
|
320
|
+
try { execSync(`chmod +x "${join(stagedScripts, sf)}"`, { stdio: 'pipe' }); } catch {}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
debugLog('DEBUG', 'hook-update', `Auto-update staged ${copied} source files`);
|
|
325
|
+
}
|
|
326
|
+
|
|
247
327
|
// ── State Persistence ──────────────────────────────────────
|
|
248
328
|
function readState() {
|
|
249
329
|
try {
|
package/hook.mjs
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { randomUUID } from 'crypto';
|
|
8
8
|
import { join, basename } from 'path';
|
|
9
9
|
import { readFileSync, writeFileSync, unlinkSync, readdirSync, renameSync, statSync } from 'fs';
|
|
10
|
+
import { homedir } from 'os';
|
|
10
11
|
import {
|
|
11
12
|
truncate, typeIcon, inferProject, detectBashSignificance,
|
|
12
13
|
extractErrorKeywords, extractFilePaths, isRelatedToEpisode,
|
|
@@ -40,6 +41,22 @@ import { checkForUpdate } from './hook-update.mjs';
|
|
|
40
41
|
// Background workers (llm-episode, llm-summary, resource-scan) are exempt — they're ours
|
|
41
42
|
const event = process.argv[2];
|
|
42
43
|
const BG_EVENTS = new Set(['llm-episode', 'llm-summary', 'resource-scan']);
|
|
44
|
+
|
|
45
|
+
// Respect Claude Code plugin disable state even when legacy settings.json hooks remain.
|
|
46
|
+
// install.mjs writes direct hooks into ~/.claude/settings.json, so disabling the plugin
|
|
47
|
+
// in Claude UI does not automatically remove them. Exit early to make disable actually work.
|
|
48
|
+
const PLUGIN_KEY = 'claude-mem-lite@sdsrss';
|
|
49
|
+
function isPluginExplicitlyDisabled() {
|
|
50
|
+
try {
|
|
51
|
+
const settingsPath = join(homedir(), '.claude', 'settings.json');
|
|
52
|
+
const settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
|
|
53
|
+
return settings.enabledPlugins?.[PLUGIN_KEY] === false;
|
|
54
|
+
} catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (event && isPluginExplicitlyDisabled()) process.exit(0);
|
|
43
60
|
if (process.env.CLAUDE_MEM_HOOK_RUNNING && !BG_EVENTS.has(event)) process.exit(0);
|
|
44
61
|
|
|
45
62
|
// Crash-safe: flush episode buffer on unexpected termination to prevent data loss
|
|
@@ -820,7 +837,10 @@ async function handleSessionStart() {
|
|
|
820
837
|
if (updateResult?.updated) {
|
|
821
838
|
process.stdout.write(`\n🔄 claude-mem-lite: v${updateResult.from} → v${updateResult.to} updated\n`);
|
|
822
839
|
} else if (updateResult?.updateAvailable) {
|
|
823
|
-
|
|
840
|
+
const hint = updateResult.installDeferred
|
|
841
|
+
? ' — plugin mode only checks for updates; reinstall/update the plugin to apply it'
|
|
842
|
+
: '';
|
|
843
|
+
process.stdout.write(`\n📦 claude-mem-lite: v${updateResult.to} available (current: v${updateResult.from})${hint}\n`);
|
|
824
844
|
}
|
|
825
845
|
} catch (e) { debugCatch(e, 'session-start-update'); }
|
|
826
846
|
|
package/install.mjs
CHANGED
|
@@ -22,6 +22,8 @@ const INSTALL_DIR = DATA_DIR;
|
|
|
22
22
|
const SERVER_PATH = join(INSTALL_DIR, 'server.mjs');
|
|
23
23
|
const HOOK_PATH = join(INSTALL_DIR, 'hook.mjs');
|
|
24
24
|
const MARKETPLACE_KEY = 'sdsrss';
|
|
25
|
+
const PLUGIN_KEY = `claude-mem-lite@${MARKETPLACE_KEY}`;
|
|
26
|
+
const NPM_INSTALL_CMD = 'npm install --omit=dev --no-audit --no-fund';
|
|
25
27
|
|
|
26
28
|
// ─── Curated Resource Metadata ───────────────────────────────────────────────
|
|
27
29
|
// Replaces generic name-echo fallback with FTS5-optimized metadata per resource.
|
|
@@ -2033,8 +2035,8 @@ function registerVirtualResources(rdb) {
|
|
|
2033
2035
|
return count;
|
|
2034
2036
|
}
|
|
2035
2037
|
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
+
let cmd = process.argv[2];
|
|
2039
|
+
let flags = new Set(process.argv.slice(3));
|
|
2038
2040
|
|
|
2039
2041
|
function log(msg) { console.log(` ${msg}`); }
|
|
2040
2042
|
function ok(msg) { console.log(` ✓ ${msg}`); }
|
|
@@ -2063,7 +2065,7 @@ async function install() {
|
|
|
2063
2065
|
'server.mjs', 'server-internals.mjs', 'tool-schemas.mjs',
|
|
2064
2066
|
'hook.mjs', 'hook-shared.mjs', 'hook-llm.mjs', 'hook-memory.mjs',
|
|
2065
2067
|
'hook-semaphore.mjs', 'hook-episode.mjs', 'hook-context.mjs', 'hook-handoff.mjs', 'hook-update.mjs',
|
|
2066
|
-
'haiku-client.mjs', 'utils.mjs', 'schema.mjs', 'package.json', 'skill.md',
|
|
2068
|
+
'haiku-client.mjs', 'utils.mjs', 'schema.mjs', 'package.json', 'package-lock.json', 'skill.md',
|
|
2067
2069
|
'registry.mjs', 'registry-scanner.mjs', 'registry-indexer.mjs',
|
|
2068
2070
|
'registry-retriever.mjs', 'resource-discovery.mjs',
|
|
2069
2071
|
'dispatch.mjs', 'dispatch-inject.mjs', 'dispatch-feedback.mjs', 'dispatch-patterns.mjs', 'dispatch-workflow.mjs',
|
|
@@ -2120,17 +2122,15 @@ async function install() {
|
|
|
2120
2122
|
// 2. npm install (skip for --dev: node_modules is symlinked)
|
|
2121
2123
|
if (IS_DEV) {
|
|
2122
2124
|
ok('Dependencies: using dev dir (symlinked)');
|
|
2123
|
-
} else
|
|
2124
|
-
log('
|
|
2125
|
+
} else {
|
|
2126
|
+
log('Ensuring dependencies installed...');
|
|
2125
2127
|
try {
|
|
2126
|
-
execSync(
|
|
2128
|
+
execSync(NPM_INSTALL_CMD, { cwd: INSTALL_DIR, stdio: 'pipe' });
|
|
2127
2129
|
ok('Dependencies installed');
|
|
2128
2130
|
} catch (e) {
|
|
2129
2131
|
fail('npm install failed: ' + e.message);
|
|
2130
2132
|
process.exit(1);
|
|
2131
2133
|
}
|
|
2132
|
-
} else {
|
|
2133
|
-
ok('Dependencies already installed');
|
|
2134
2134
|
}
|
|
2135
2135
|
|
|
2136
2136
|
// 3. Register MCP server
|
|
@@ -2187,6 +2187,9 @@ async function install() {
|
|
|
2187
2187
|
// 4. Configure hooks (merge: preserve user's existing hooks, replace ours)
|
|
2188
2188
|
log('Configuring hooks...');
|
|
2189
2189
|
const settings = readSettings();
|
|
2190
|
+
if (clearPluginDisabledMarkerForDirectInstall(settings)) {
|
|
2191
|
+
ok('Cleared stale disabled plugin flag so install.mjs-managed hooks can run');
|
|
2192
|
+
}
|
|
2190
2193
|
settings.hooks = settings.hooks || {};
|
|
2191
2194
|
|
|
2192
2195
|
const PREFILTER_PATH = join(INSTALL_DIR, 'scripts', 'post-tool-use.sh');
|
|
@@ -2543,74 +2546,72 @@ async function uninstall() {
|
|
|
2543
2546
|
|
|
2544
2547
|
// 2. Remove hooks from settings.json (match both npx and git-clone install paths)
|
|
2545
2548
|
const settings = readSettings();
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2549
|
+
cleanupMemHooksFromSettings(settings);
|
|
2550
|
+
|
|
2551
|
+
// 3. Clean plugin registry entries conservatively (avoid deleting other plugins
|
|
2552
|
+
// from the same marketplace publisher)
|
|
2553
|
+
const pluginsDir = join(homedir(), '.claude', 'plugins');
|
|
2554
|
+
const installedPath = join(pluginsDir, 'installed_plugins.json');
|
|
2555
|
+
let canRemoveMarketplaceArtifacts;
|
|
2556
|
+
try {
|
|
2557
|
+
const installed = JSON.parse(readFileSync(installedPath, 'utf8'));
|
|
2558
|
+
const plugins = getInstalledPluginEntries(installed);
|
|
2559
|
+
let cleaned = false;
|
|
2560
|
+
if (PLUGIN_KEY in plugins) {
|
|
2561
|
+
delete plugins[PLUGIN_KEY];
|
|
2562
|
+
cleaned = true;
|
|
2551
2563
|
}
|
|
2552
|
-
|
|
2564
|
+
canRemoveMarketplaceArtifacts = !hasOtherMarketplacePlugins(installed);
|
|
2565
|
+
if (cleaned) {
|
|
2566
|
+
writeFileSync(installedPath, JSON.stringify(installed, null, 2) + '\n');
|
|
2567
|
+
ok('Removed from installed_plugins.json');
|
|
2568
|
+
}
|
|
2569
|
+
} catch {
|
|
2570
|
+
// Conservative default: if registry shape is unknown, preserve marketplace cache.
|
|
2571
|
+
canRemoveMarketplaceArtifacts = false;
|
|
2553
2572
|
}
|
|
2554
2573
|
|
|
2555
|
-
//
|
|
2556
|
-
const pluginKey = `claude-mem-lite@${MARKETPLACE_KEY}`;
|
|
2574
|
+
// 4. Clean plugin system entries from settings.json
|
|
2557
2575
|
const marketplaceKey = MARKETPLACE_KEY;
|
|
2558
2576
|
if (settings.enabledPlugins) {
|
|
2559
|
-
delete settings.enabledPlugins[
|
|
2577
|
+
delete settings.enabledPlugins[PLUGIN_KEY];
|
|
2560
2578
|
}
|
|
2561
|
-
if (settings.extraKnownMarketplaces) {
|
|
2579
|
+
if (settings.extraKnownMarketplaces && canRemoveMarketplaceArtifacts) {
|
|
2562
2580
|
delete settings.extraKnownMarketplaces[marketplaceKey];
|
|
2563
2581
|
}
|
|
2564
2582
|
writeSettings(settings);
|
|
2565
2583
|
ok('Hooks and plugin settings cleaned');
|
|
2566
2584
|
|
|
2567
|
-
//
|
|
2568
|
-
const pluginsDir = join(homedir(), '.claude', 'plugins');
|
|
2569
|
-
|
|
2570
|
-
// 4a. Remove marketplace directory
|
|
2585
|
+
// 5. Clean plugin system registry files (only if no other marketplace plugins remain)
|
|
2571
2586
|
const marketplaceDir = join(pluginsDir, 'marketplaces', marketplaceKey);
|
|
2572
|
-
if (existsSync(marketplaceDir)) {
|
|
2587
|
+
if (canRemoveMarketplaceArtifacts && existsSync(marketplaceDir)) {
|
|
2573
2588
|
rmSync(marketplaceDir, { recursive: true, force: true });
|
|
2574
2589
|
ok('Marketplace directory removed');
|
|
2575
2590
|
}
|
|
2576
2591
|
|
|
2577
|
-
//
|
|
2592
|
+
// 5b. Remove cache directory
|
|
2578
2593
|
const cacheDir = join(pluginsDir, 'cache', marketplaceKey);
|
|
2579
|
-
if (existsSync(cacheDir)) {
|
|
2594
|
+
if (canRemoveMarketplaceArtifacts && existsSync(cacheDir)) {
|
|
2580
2595
|
rmSync(cacheDir, { recursive: true, force: true });
|
|
2581
2596
|
ok('Plugin cache removed');
|
|
2582
2597
|
}
|
|
2583
2598
|
|
|
2584
|
-
//
|
|
2599
|
+
// 5c. Clean known_marketplaces.json
|
|
2585
2600
|
const knownPath = join(pluginsDir, 'known_marketplaces.json');
|
|
2586
2601
|
try {
|
|
2587
2602
|
const known = JSON.parse(readFileSync(knownPath, 'utf8'));
|
|
2588
|
-
if (marketplaceKey in known) {
|
|
2603
|
+
if (canRemoveMarketplaceArtifacts && marketplaceKey in known) {
|
|
2589
2604
|
delete known[marketplaceKey];
|
|
2590
2605
|
writeFileSync(knownPath, JSON.stringify(known, null, 2) + '\n');
|
|
2591
2606
|
ok('Removed from known_marketplaces.json');
|
|
2592
2607
|
}
|
|
2593
2608
|
} catch { /* file may not exist */ }
|
|
2594
2609
|
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
const installed = JSON.parse(readFileSync(installedPath, 'utf8'));
|
|
2599
|
-
const plugins = installed.plugins || installed;
|
|
2600
|
-
let cleaned = false;
|
|
2601
|
-
for (const key of Object.keys(plugins)) {
|
|
2602
|
-
if (key.includes('claude-mem-lite') || key.includes('sdsrss')) {
|
|
2603
|
-
delete plugins[key];
|
|
2604
|
-
cleaned = true;
|
|
2605
|
-
}
|
|
2606
|
-
}
|
|
2607
|
-
if (cleaned) {
|
|
2608
|
-
writeFileSync(installedPath, JSON.stringify(installed, null, 2) + '\n');
|
|
2609
|
-
ok('Removed from installed_plugins.json');
|
|
2610
|
-
}
|
|
2611
|
-
} catch { /* file may not exist */ }
|
|
2610
|
+
if (!canRemoveMarketplaceArtifacts && (existsSync(marketplaceDir) || existsSync(cacheDir))) {
|
|
2611
|
+
log('Marketplace cache preserved (other plugins may still depend on sdsrss marketplace)');
|
|
2612
|
+
}
|
|
2612
2613
|
|
|
2613
|
-
//
|
|
2614
|
+
// 6. Purge data if requested
|
|
2614
2615
|
if (flags.has('--purge')) {
|
|
2615
2616
|
const expectedPurgePath = join(homedir(), '.claude-mem-lite');
|
|
2616
2617
|
if (existsSync(DATA_DIR) && DATA_DIR === expectedPurgePath) {
|
|
@@ -2626,6 +2627,24 @@ async function uninstall() {
|
|
|
2626
2627
|
console.log('\n Done!\n');
|
|
2627
2628
|
}
|
|
2628
2629
|
|
|
2630
|
+
// ─── Cleanup Hooks ───────────────────────────────────────────────────────────
|
|
2631
|
+
|
|
2632
|
+
async function cleanupHooks() {
|
|
2633
|
+
console.log('\nclaude-mem-lite cleanup-hooks\n');
|
|
2634
|
+
|
|
2635
|
+
const settings = readSettings();
|
|
2636
|
+
const removed = cleanupMemHooksFromSettings(settings);
|
|
2637
|
+
|
|
2638
|
+
if (removed > 0) {
|
|
2639
|
+
writeSettings(settings);
|
|
2640
|
+
ok(`Removed ${removed} claude-mem-lite hook configuration${removed === 1 ? '' : 's'} from settings.json`);
|
|
2641
|
+
} else {
|
|
2642
|
+
ok('No claude-mem-lite hooks found in settings.json');
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
console.log('');
|
|
2646
|
+
}
|
|
2647
|
+
|
|
2629
2648
|
// ─── Status ─────────────────────────────────────────────────────────────────
|
|
2630
2649
|
|
|
2631
2650
|
async function status() {
|
|
@@ -2645,11 +2664,24 @@ async function status() {
|
|
|
2645
2664
|
|
|
2646
2665
|
// Hooks
|
|
2647
2666
|
const settings = readSettings();
|
|
2648
|
-
const hasHooks =
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2667
|
+
const hasHooks = hasMemHooksConfigured(settings);
|
|
2668
|
+
const pluginDisabled = isPluginExplicitlyDisabled(settings);
|
|
2669
|
+
const pluginEnabled = settings.enabledPlugins?.[PLUGIN_KEY] === true;
|
|
2670
|
+
|
|
2671
|
+
if (pluginEnabled) {
|
|
2672
|
+
ok('Plugin: enabled in settings');
|
|
2673
|
+
} else if (pluginDisabled) {
|
|
2674
|
+
warn('Plugin: disabled in settings');
|
|
2675
|
+
} else {
|
|
2676
|
+
warn('Plugin: not present in enabledPlugins');
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2679
|
+
if (hasHooks && pluginDisabled) {
|
|
2680
|
+
warn('Hooks: still configured in settings.json while plugin is disabled (runtime ignores them; run cleanup-hooks or uninstall to clean up)');
|
|
2681
|
+
} else if (hasHooks) {
|
|
2652
2682
|
ok('Hooks: configured');
|
|
2683
|
+
} else if (pluginDisabled) {
|
|
2684
|
+
ok('Hooks: not configured');
|
|
2653
2685
|
} else {
|
|
2654
2686
|
fail('Hooks: not configured');
|
|
2655
2687
|
}
|
|
@@ -2727,6 +2759,21 @@ async function doctor() {
|
|
|
2727
2759
|
issues++;
|
|
2728
2760
|
}
|
|
2729
2761
|
|
|
2762
|
+
// Plugin/hook lifecycle state
|
|
2763
|
+
const settings = readSettings();
|
|
2764
|
+
const hasHooks = hasMemHooksConfigured(settings);
|
|
2765
|
+
const pluginDisabled = isPluginExplicitlyDisabled(settings);
|
|
2766
|
+
if (pluginDisabled && hasHooks) {
|
|
2767
|
+
fail('Plugin lifecycle: plugin is disabled but claude-mem-lite hooks still remain in settings.json');
|
|
2768
|
+
issues++;
|
|
2769
|
+
} else if (pluginDisabled) {
|
|
2770
|
+
ok('Plugin lifecycle: disabled cleanly (no active mem hooks)');
|
|
2771
|
+
} else if (hasHooks) {
|
|
2772
|
+
ok('Plugin lifecycle: hooks active');
|
|
2773
|
+
} else {
|
|
2774
|
+
warn('Plugin lifecycle: hooks not configured');
|
|
2775
|
+
}
|
|
2776
|
+
|
|
2730
2777
|
// Database
|
|
2731
2778
|
if (existsSync(DB_PATH)) {
|
|
2732
2779
|
try {
|
|
@@ -2799,6 +2846,50 @@ function isMemHook(cfg) {
|
|
|
2799
2846
|
});
|
|
2800
2847
|
}
|
|
2801
2848
|
|
|
2849
|
+
function hasMemHooksConfigured(settings) {
|
|
2850
|
+
if (!settings?.hooks) return false;
|
|
2851
|
+
return Object.values(settings.hooks).some(configs =>
|
|
2852
|
+
Array.isArray(configs) && configs.some(cfg => isMemHook(cfg))
|
|
2853
|
+
);
|
|
2854
|
+
}
|
|
2855
|
+
|
|
2856
|
+
export function clearPluginDisabledMarkerForDirectInstall(settings) {
|
|
2857
|
+
if (settings?.enabledPlugins?.[PLUGIN_KEY] !== false) return false;
|
|
2858
|
+
delete settings.enabledPlugins[PLUGIN_KEY];
|
|
2859
|
+
if (Object.keys(settings.enabledPlugins).length === 0) delete settings.enabledPlugins;
|
|
2860
|
+
return true;
|
|
2861
|
+
}
|
|
2862
|
+
|
|
2863
|
+
function cleanupMemHooksFromSettings(settings) {
|
|
2864
|
+
if (!settings?.hooks) return 0;
|
|
2865
|
+
|
|
2866
|
+
let removed = 0;
|
|
2867
|
+
for (const [event, configs] of Object.entries(settings.hooks)) {
|
|
2868
|
+
if (!Array.isArray(configs)) continue;
|
|
2869
|
+
const kept = configs.filter(cfg => !isMemHook(cfg));
|
|
2870
|
+
removed += configs.length - kept.length;
|
|
2871
|
+
if (kept.length > 0) settings.hooks[event] = kept;
|
|
2872
|
+
else delete settings.hooks[event];
|
|
2873
|
+
}
|
|
2874
|
+
|
|
2875
|
+
if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
|
|
2876
|
+
return removed;
|
|
2877
|
+
}
|
|
2878
|
+
|
|
2879
|
+
function isPluginExplicitlyDisabled(settings) {
|
|
2880
|
+
return settings?.enabledPlugins?.[PLUGIN_KEY] === false;
|
|
2881
|
+
}
|
|
2882
|
+
|
|
2883
|
+
function getInstalledPluginEntries(installed) {
|
|
2884
|
+
if (installed?.plugins && typeof installed.plugins === 'object') return installed.plugins;
|
|
2885
|
+
return installed && typeof installed === 'object' ? installed : {};
|
|
2886
|
+
}
|
|
2887
|
+
|
|
2888
|
+
export function hasOtherMarketplacePlugins(installed, marketplaceKey = MARKETPLACE_KEY, pluginKey = PLUGIN_KEY) {
|
|
2889
|
+
const plugins = getInstalledPluginEntries(installed);
|
|
2890
|
+
return Object.keys(plugins).some(key => key !== pluginKey && key.endsWith(`@${marketplaceKey}`));
|
|
2891
|
+
}
|
|
2892
|
+
|
|
2802
2893
|
function readSettings() {
|
|
2803
2894
|
try {
|
|
2804
2895
|
return JSON.parse(readFileSync(SETTINGS_PATH, 'utf8'));
|
|
@@ -2823,10 +2914,12 @@ async function manualUpdate() {
|
|
|
2823
2914
|
// Force check by importing hook-update (bypasses throttle for manual use)
|
|
2824
2915
|
const { checkForUpdate, getCurrentVersion } = await import('./hook-update.mjs');
|
|
2825
2916
|
log('Checking for updates...');
|
|
2826
|
-
const result = await checkForUpdate();
|
|
2917
|
+
const result = await checkForUpdate({ force: true, allowInstall: true });
|
|
2827
2918
|
|
|
2828
2919
|
if (result?.updated) {
|
|
2829
2920
|
ok(`Updated: v${result.from} → v${result.to}`);
|
|
2921
|
+
} else if (result?.updateAvailable && result?.installDeferred) {
|
|
2922
|
+
warn(`v${result.to} available — plugin mode only checks for updates, reinstall/update the plugin to apply it`);
|
|
2830
2923
|
} else if (result?.updateAvailable) {
|
|
2831
2924
|
warn(`v${result.to} available but install failed — try: node install.mjs install`);
|
|
2832
2925
|
} else {
|
|
@@ -2881,31 +2974,38 @@ function syncVersions() {
|
|
|
2881
2974
|
|
|
2882
2975
|
// ─── Main ───────────────────────────────────────────────────────────────────
|
|
2883
2976
|
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
break;
|
|
2891
|
-
case 'status':
|
|
2892
|
-
await status();
|
|
2893
|
-
break;
|
|
2894
|
-
case 'doctor':
|
|
2895
|
-
await doctor();
|
|
2896
|
-
break;
|
|
2897
|
-
case 'update':
|
|
2898
|
-
await manualUpdate();
|
|
2899
|
-
break;
|
|
2900
|
-
case 'release':
|
|
2901
|
-
syncVersions();
|
|
2902
|
-
break;
|
|
2903
|
-
default:
|
|
2904
|
-
if (IS_NPX) {
|
|
2905
|
-
// npx claude-mem-lite (no args) → auto install
|
|
2977
|
+
export async function main(argv = process.argv.slice(2)) {
|
|
2978
|
+
cmd = argv[0];
|
|
2979
|
+
flags = new Set(argv.slice(1));
|
|
2980
|
+
|
|
2981
|
+
switch (cmd) {
|
|
2982
|
+
case 'install':
|
|
2906
2983
|
await install();
|
|
2907
|
-
|
|
2908
|
-
|
|
2984
|
+
break;
|
|
2985
|
+
case 'uninstall':
|
|
2986
|
+
await uninstall();
|
|
2987
|
+
break;
|
|
2988
|
+
case 'status':
|
|
2989
|
+
await status();
|
|
2990
|
+
break;
|
|
2991
|
+
case 'doctor':
|
|
2992
|
+
await doctor();
|
|
2993
|
+
break;
|
|
2994
|
+
case 'cleanup-hooks':
|
|
2995
|
+
await cleanupHooks();
|
|
2996
|
+
break;
|
|
2997
|
+
case 'update':
|
|
2998
|
+
await manualUpdate();
|
|
2999
|
+
break;
|
|
3000
|
+
case 'release':
|
|
3001
|
+
syncVersions();
|
|
3002
|
+
break;
|
|
3003
|
+
default:
|
|
3004
|
+
if (IS_NPX) {
|
|
3005
|
+
// npx claude-mem-lite (no args) → auto install
|
|
3006
|
+
await install();
|
|
3007
|
+
} else {
|
|
3008
|
+
console.log(`
|
|
2909
3009
|
claude-mem-lite — Lightweight memory system for Claude Code
|
|
2910
3010
|
|
|
2911
3011
|
Usage:
|
|
@@ -2915,10 +3015,15 @@ Usage:
|
|
|
2915
3015
|
node install.mjs uninstall --purge Remove and delete all data
|
|
2916
3016
|
node install.mjs status Show current status
|
|
2917
3017
|
node install.mjs doctor Diagnose issues
|
|
3018
|
+
node install.mjs cleanup-hooks Remove only claude-mem-lite hooks from settings.json
|
|
2918
3019
|
node install.mjs update Check for and install updates
|
|
2919
3020
|
node install.mjs release Sync version to plugin.json + marketplace.json
|
|
2920
3021
|
|
|
2921
3022
|
npx claude-mem-lite Install via npx (one-liner)
|
|
2922
3023
|
`);
|
|
2923
|
-
|
|
3024
|
+
}
|
|
3025
|
+
}
|
|
2924
3026
|
}
|
|
3027
|
+
|
|
3028
|
+
const IS_MAIN = process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
3029
|
+
if (IS_MAIN) await main();
|