claude-code-memory-explorer 0.3.0 → 0.4.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 +1 -1
- package/public/app.js +20 -0
- package/server.js +25 -14
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -871,6 +871,26 @@ function _hubNavigate(app, url) {
|
|
|
871
871
|
window.parent?.postMessage({ type: 'hub:navigate', app, url }, '*');
|
|
872
872
|
}
|
|
873
873
|
|
|
874
|
+
(function initHubTheme() {
|
|
875
|
+
const getTheme = () => (document.body.classList.contains('light') ? 'light' : 'dark');
|
|
876
|
+
const hubOrigin = () => (window.__HUB__?.url ? new URL(window.__HUB__.url).origin : null);
|
|
877
|
+
let lastTheme = getTheme();
|
|
878
|
+
window.addEventListener('message', (e) => {
|
|
879
|
+
if (e.source !== window.parent || e.origin !== hubOrigin()) return;
|
|
880
|
+
if (e.data?.type !== 'hub:theme') return;
|
|
881
|
+
if (getTheme() === e.data.theme) return;
|
|
882
|
+
window.toggleTheme();
|
|
883
|
+
lastTheme = getTheme();
|
|
884
|
+
});
|
|
885
|
+
new MutationObserver(() => {
|
|
886
|
+
const t = getTheme();
|
|
887
|
+
if (t === lastTheme) return;
|
|
888
|
+
lastTheme = t;
|
|
889
|
+
const origin = hubOrigin();
|
|
890
|
+
if (origin) window.parent.postMessage({ type: 'hub:theme', theme: t }, origin);
|
|
891
|
+
}).observe(document.body, { attributes: true, attributeFilter: ['class'] });
|
|
892
|
+
})();
|
|
893
|
+
|
|
874
894
|
// #endregion HUB_INTEGRATION
|
|
875
895
|
|
|
876
896
|
// #region RESIZE
|
package/server.js
CHANGED
|
@@ -17,21 +17,19 @@ function getArg(name) {
|
|
|
17
17
|
return process.argv[eqIdx + 1] || null;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
const expandHome = (p) => (p || '').replace(/^~/, os.homedir());
|
|
21
|
+
|
|
20
22
|
const PORT = getArg('port') || process.env.PORT || 3459;
|
|
21
23
|
const AUTO_OPEN = process.argv.includes('--open');
|
|
22
24
|
const claudeDirArg = getArg('dir');
|
|
23
|
-
const CLAUDE_DIR = claudeDirArg
|
|
24
|
-
? claudeDirArg.replace(/^~/, os.homedir())
|
|
25
|
-
: path.join(os.homedir(), '.claude');
|
|
25
|
+
const CLAUDE_DIR = claudeDirArg ? expandHome(claudeDirArg) : path.join(os.homedir(), '.claude');
|
|
26
26
|
const projectDirArg = getArg('project');
|
|
27
27
|
|
|
28
28
|
// #endregion CLI_ARGS
|
|
29
29
|
|
|
30
30
|
// #region STATE
|
|
31
31
|
|
|
32
|
-
let currentProjectPath = projectDirArg
|
|
33
|
-
? path.resolve(projectDirArg.replace(/^~/, os.homedir()))
|
|
34
|
-
: process.cwd();
|
|
32
|
+
let currentProjectPath = projectDirArg ? path.resolve(expandHome(projectDirArg)) : process.cwd();
|
|
35
33
|
|
|
36
34
|
const cache = {};
|
|
37
35
|
const CACHE_TTL = 30_000;
|
|
@@ -129,7 +127,7 @@ function parseImports(content) {
|
|
|
129
127
|
function resolveImport(importPath, fromFile) {
|
|
130
128
|
let resolved = importPath;
|
|
131
129
|
if (resolved.startsWith('~')) {
|
|
132
|
-
resolved = resolved
|
|
130
|
+
resolved = expandHome(resolved);
|
|
133
131
|
} else {
|
|
134
132
|
resolved = path.resolve(path.dirname(fromFile), resolved);
|
|
135
133
|
}
|
|
@@ -325,7 +323,7 @@ function discoverMemorySources(projectPath) {
|
|
|
325
323
|
}
|
|
326
324
|
}
|
|
327
325
|
|
|
328
|
-
// 6. Auto memory
|
|
326
|
+
// 6. Auto memory (projects base honors `autoMemoryDirectory` user setting)
|
|
329
327
|
const memoryDir = findMemoryDir(projectPath);
|
|
330
328
|
if (memoryDir && fs.existsSync(memoryDir)) {
|
|
331
329
|
pushMemoryDir(memoryDir, 'memory', 'memory');
|
|
@@ -428,7 +426,7 @@ function getAncestorDirs(projectPath) {
|
|
|
428
426
|
}
|
|
429
427
|
|
|
430
428
|
function findMemoryDir(projectPath) {
|
|
431
|
-
const projectsDir =
|
|
429
|
+
const projectsDir = getProjectsBaseDir();
|
|
432
430
|
if (!fs.existsSync(projectsDir)) return null;
|
|
433
431
|
const encoded = encodeProjectPath(projectPath);
|
|
434
432
|
const memDir = path.join(projectsDir, encoded, 'memory');
|
|
@@ -467,8 +465,17 @@ function encodeProjectPath(projectPath) {
|
|
|
467
465
|
.replace(/\//g, '-');
|
|
468
466
|
}
|
|
469
467
|
|
|
468
|
+
// User-scope `autoMemoryDirectory` only — Claude Code rejects this key from project/local settings for security.
|
|
469
|
+
function getProjectsBaseDir() {
|
|
470
|
+
let settings = {};
|
|
471
|
+
try { settings = JSON.parse(fs.readFileSync(path.join(CLAUDE_DIR, 'settings.json'), 'utf-8')); } catch { /* no settings */ }
|
|
472
|
+
const raw = settings.autoMemoryDirectory;
|
|
473
|
+
if (typeof raw === 'string' && raw.trim()) return path.resolve(expandHome(raw));
|
|
474
|
+
return path.join(CLAUDE_DIR, 'projects');
|
|
475
|
+
}
|
|
476
|
+
|
|
470
477
|
function findMemoryDirBySubstring(projectPath) {
|
|
471
|
-
const projectsDir =
|
|
478
|
+
const projectsDir = getProjectsBaseDir();
|
|
472
479
|
if (!fs.existsSync(projectsDir)) return null;
|
|
473
480
|
// Match by last 2-3 path segments
|
|
474
481
|
const segments = projectPath.replace(/\\/g, '/').split('/').filter(Boolean);
|
|
@@ -525,7 +532,7 @@ app.get('/api/project', (_req, res) => {
|
|
|
525
532
|
app.put('/api/project', (req, res) => {
|
|
526
533
|
const { path: dirPath } = req.body;
|
|
527
534
|
if (!dirPath) return res.status(400).json({ error: 'path required' });
|
|
528
|
-
const resolved = path.resolve(dirPath
|
|
535
|
+
const resolved = path.resolve(expandHome(dirPath));
|
|
529
536
|
if (!fs.existsSync(resolved)) return res.status(404).json({ error: 'directory not found' });
|
|
530
537
|
if (resolved !== path.resolve(currentProjectPath)) {
|
|
531
538
|
currentProjectPath = resolved;
|
|
@@ -541,8 +548,12 @@ app.post('/api/refresh', (_req, res) => {
|
|
|
541
548
|
|
|
542
549
|
function resolveAllowedPath(filePath) {
|
|
543
550
|
if (!filePath) return { error: { status: 400, message: 'path required' } };
|
|
544
|
-
const resolved = path.resolve(filePath
|
|
545
|
-
const roots = [
|
|
551
|
+
const resolved = path.resolve(expandHome(filePath));
|
|
552
|
+
const roots = [
|
|
553
|
+
path.resolve(CLAUDE_DIR),
|
|
554
|
+
path.resolve(currentProjectPath),
|
|
555
|
+
getProjectsBaseDir(),
|
|
556
|
+
];
|
|
546
557
|
const inRoot = roots.some((r) => resolved === r || resolved.startsWith(r + path.sep));
|
|
547
558
|
if (!inRoot) return { error: { status: 403, message: 'path outside allowed roots' } };
|
|
548
559
|
return { resolved };
|
|
@@ -597,7 +608,7 @@ app.post('/api/memory/cleanup-orphans', (req, res) => {
|
|
|
597
608
|
app.post('/api/open-in-editor', (req, res) => {
|
|
598
609
|
const { path: filePath } = req.body;
|
|
599
610
|
if (!filePath) return res.status(400).json({ error: 'path required' });
|
|
600
|
-
const resolved = filePath
|
|
611
|
+
const resolved = expandHome(filePath);
|
|
601
612
|
if (!fs.existsSync(resolved)) return res.status(404).json({ error: 'file not found' });
|
|
602
613
|
const { exec } = require('child_process');
|
|
603
614
|
exec(`code "${resolved}"`, (err) => {
|