claude-code-memory-explorer 0.6.0 → 0.9.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 +24 -66
- package/package.json +1 -1
- package/public/app.js +71 -24
- package/public/style.css +4 -1
- package/server.js +2 -2
package/README.md
CHANGED
|
@@ -4,7 +4,11 @@
|
|
|
4
4
|
[](LICENSE)
|
|
5
5
|
[](https://www.npmjs.com/package/claude-code-memory-explorer)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**[Live Demo & Docs](https://nikiforovall.blog/claude-code-memory/)**
|
|
8
|
+
|
|
9
|
+
> See everything Claude Code knows about your project — CLAUDE.md files, rules, auto memory, agent memory, and imports.
|
|
10
|
+
|
|
11
|
+

|
|
8
12
|
|
|
9
13
|
## Getting Started
|
|
10
14
|
|
|
@@ -12,81 +16,35 @@
|
|
|
12
16
|
npx claude-code-memory-explorer --open
|
|
13
17
|
```
|
|
14
18
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
That's it. No config — the dashboard reads your existing Claude Code memory files.
|
|
18
|
-
|
|
19
|
-

|
|
20
|
-

|
|
19
|
+
That's it — no config. The dashboard scans the Claude Code memory locations for the current project and renders the full stack. Completely read-only; nothing leaves your machine.
|
|
21
20
|
|
|
22
21
|
## Features
|
|
23
22
|
|
|
24
|
-
- **Full memory stack** —
|
|
25
|
-
- **Import resolution** —
|
|
26
|
-
- **Rules inspection** —
|
|
27
|
-
- **Auto memory** —
|
|
28
|
-
- **
|
|
29
|
-
- **Keyboard-driven** — j/k navigation, h/l group jump, e to open in editor,
|
|
30
|
-
- **
|
|
31
|
-
- **
|
|
32
|
-
- **Dark & light theme** — Dark default, light toggle with `t` key
|
|
33
|
-
- **Hub integration** — Works as a tab in Claude Code Hub alongside Cost and Marketplace
|
|
23
|
+
- **Full memory stack** — user CLAUDE.md, project CLAUDE.md, CLAUDE.local.md, rules, auto memory, agent memory, and managed policies in one view
|
|
24
|
+
- **Import resolution** — follows `@path/to/file.md` references and `[text](file.md)` markdown links up to 5 levels deep, clickable in the preview
|
|
25
|
+
- **Rules inspection** — path-scoped frontmatter (`paths`, `type`, `name`) with conditional load indicators
|
|
26
|
+
- **Auto memory** — MEMORY.md with startup badge, on-demand topic files, and frontmatter badges
|
|
27
|
+
- **Agent memory** — subagent persistent memory at user, project, and local scope, grouped per agent
|
|
28
|
+
- **Keyboard-driven** — j/k navigation, h/l group jump, e to open in editor, Shift+P project picker, ? for help
|
|
29
|
+
- **17 color themes** — Ember, Gruvbox, Catppuccin, Tokyo Night, Dracula, Nord, and more — each in light and dark, PWA installable
|
|
30
|
+
- **Hub integration** — runs standalone or as a tab in [Claude Code Hub](https://github.com/NikiforovAll/claude-code-hub) alongside Kanban, Cost, and Marketplace
|
|
34
31
|
|
|
35
|
-
|
|
32
|
+

|
|
36
33
|
|
|
37
|
-
|
|
38
|
-
PORT=8080 npx claude-code-memory-explorer # Custom port
|
|
39
|
-
npx claude-code-memory-explorer --open # Auto-open browser
|
|
40
|
-
npx claude-code-memory-explorer --dir=~/.claude-work # Custom Claude config dir
|
|
41
|
-
npx claude-code-memory-explorer --project=/path/to/project # Specify project path
|
|
42
|
-
```
|
|
34
|
+

|
|
43
35
|
|
|
44
|
-
|
|
36
|
+

|
|
45
37
|
|
|
46
|
-
|
|
38
|
+
## Configuration
|
|
47
39
|
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
claude
|
|
40
|
+
```
|
|
41
|
+
PORT=8080 Custom port (default: 3544, falls back if busy)
|
|
42
|
+
--dir <path> Custom Claude config dir (default: ~/.claude)
|
|
43
|
+
--project <path> Project to inspect (default: current directory)
|
|
44
|
+
--open Open browser on start
|
|
51
45
|
```
|
|
52
46
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
Claude Code stores memory across multiple locations:
|
|
56
|
-
|
|
57
|
-
| Source | Path | Load Behavior |
|
|
58
|
-
|--------|------|---------------|
|
|
59
|
-
| Managed policy | `/etc/claude-code/CLAUDE.md` | Always |
|
|
60
|
-
| User CLAUDE.md | `~/.claude/CLAUDE.md` | Always |
|
|
61
|
-
| Project CLAUDE.md | `./CLAUDE.md` or `./.claude/CLAUDE.md` | Always |
|
|
62
|
-
| CLAUDE.local.md | `./CLAUDE.local.md` | Always |
|
|
63
|
-
| Rules | `.claude/rules/*.md` | Conditional (path-scoped) |
|
|
64
|
-
| Auto memory | `~/.claude/projects/<encoded>/memory/` | Startup (MEMORY.md) or on-demand |
|
|
65
|
-
| Agent memory | `~/.claude/agent-memory/<agent>/`, `./.claude/agent-memory/<agent>/`, `./.claude/agent-memory-local/<agent>/` | Startup (MEMORY.md) or on-demand |
|
|
66
|
-
|
|
67
|
-
The dashboard:
|
|
68
|
-
1. **Scans** all memory locations — `~/.claude/`, ancestor directories, project rules, auto memory
|
|
69
|
-
2. **Parses** YAML frontmatter in rules files for path globs and metadata
|
|
70
|
-
3. **Resolves** `@import` chains recursively (max 5 levels), tracking both hard imports and soft markdown links
|
|
71
|
-
4. **Groups** sources by scope with parent-child nesting for imports
|
|
72
|
-
5. **Renders** with Highlight.js syntax highlighting — no build step, vanilla JS
|
|
73
|
-
|
|
74
|
-
Nothing is modified — the dashboard is read-only.
|
|
75
|
-
|
|
76
|
-
### Memory Path Encoding
|
|
77
|
-
|
|
78
|
-
Claude Code encodes project paths for auto memory storage: `C:\Users\me\dev\myproject` becomes `C--Users-me-dev-myproject` under `~/.claude/projects/`. The dashboard tries exact match first, then falls back to substring matching.
|
|
79
|
-
|
|
80
|
-
## FAQ
|
|
81
|
-
|
|
82
|
-
**Does it modify any files?**
|
|
83
|
-
No. Completely read-only — only reads markdown files from the Claude Code memory locations.
|
|
84
|
-
|
|
85
|
-
**Does it work with Claude Code Hub?**
|
|
86
|
-
Yes. Exposes `/hub-config` endpoint for hub tab integration.
|
|
87
|
-
|
|
88
|
-
**Can I switch projects?**
|
|
89
|
-
Yes. Use the project picker (Shift+P) or pass `?project=/path` as a URL parameter.
|
|
47
|
+
The config dir can also be set via the `CLAUDE_CONFIG_DIR` environment variable. Switch projects at runtime with the picker (Shift+P) or `?project=/path`.
|
|
90
48
|
|
|
91
49
|
## License
|
|
92
50
|
|
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -137,7 +137,7 @@ function buildThemeMenu() {
|
|
|
137
137
|
const menu = document.getElementById('themeMenu');
|
|
138
138
|
menu.innerHTML = COLOR_THEMES.map(
|
|
139
139
|
([id, label]) =>
|
|
140
|
-
`<button type="button" class="theme-menu-item" data-theme-id="${id}"
|
|
140
|
+
`<button type="button" class="theme-menu-item theme-swatch-${id}" data-theme-id="${id}"
|
|
141
141
|
onclick="event.stopPropagation(); setColorTheme('${id}'); toggleThemeMenu()">
|
|
142
142
|
<span class="theme-swatch theme-swatch-${id}"><i class="sw-bg"></i><i class="sw-accent"></i><i class="sw-ink"></i></span>${label}
|
|
143
143
|
</button>`,
|
|
@@ -194,6 +194,20 @@ async function loadProject() {
|
|
|
194
194
|
document.getElementById('projectBtn').title = projectData.path;
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
+
// Shared by the project picker, the boot restore, and the hub project shim. Throws with the
|
|
198
|
+
// server's error message so callers can surface it directly.
|
|
199
|
+
async function putProject(dirPath) {
|
|
200
|
+
const res = await fetch('/api/project', {
|
|
201
|
+
method: 'PUT',
|
|
202
|
+
headers: { 'Content-Type': 'application/json' },
|
|
203
|
+
body: JSON.stringify({ path: dirPath }),
|
|
204
|
+
});
|
|
205
|
+
if (!res.ok) {
|
|
206
|
+
const err = await res.json().catch(() => ({}));
|
|
207
|
+
throw new Error(err.error || `${res.status}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
197
211
|
function changeProject() {
|
|
198
212
|
const current = document.getElementById('projectBtn').title;
|
|
199
213
|
document.getElementById('projectPathInput').value = current;
|
|
@@ -209,20 +223,10 @@ async function submitProjectPicker() {
|
|
|
209
223
|
btn.disabled = true;
|
|
210
224
|
btn.textContent = 'Switching...';
|
|
211
225
|
try {
|
|
212
|
-
|
|
213
|
-
method: 'PUT',
|
|
214
|
-
headers: { 'Content-Type': 'application/json' },
|
|
215
|
-
body: JSON.stringify({ path: dirPath }),
|
|
216
|
-
});
|
|
217
|
-
if (!res.ok) {
|
|
218
|
-
const err = await res.json();
|
|
219
|
-
showToast(err.error, 'error');
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
226
|
+
await putProject(dirPath);
|
|
222
227
|
closeModal('projectPickerModal');
|
|
223
228
|
addRecentProject(dirPath);
|
|
224
|
-
await loadProject();
|
|
225
|
-
await loadData();
|
|
229
|
+
await Promise.all([loadProject(), loadData()]);
|
|
226
230
|
showToast('Project switched', 'success');
|
|
227
231
|
} catch (err) {
|
|
228
232
|
showToast(err.message, 'error');
|
|
@@ -977,14 +981,24 @@ document.addEventListener('keydown', (e) => {
|
|
|
977
981
|
.catch(() => ({}));
|
|
978
982
|
if (!cfg.enabled) return;
|
|
979
983
|
window.__HUB__ = cfg;
|
|
984
|
+
const fwd = (e) =>
|
|
985
|
+
window.parent?.postMessage(
|
|
986
|
+
{ type: 'hub:keydown', key: e.key, ctrl: e.ctrlKey, alt: e.altKey, shift: e.shiftKey },
|
|
987
|
+
'*',
|
|
988
|
+
);
|
|
980
989
|
document.addEventListener('keydown', (e) => {
|
|
981
990
|
if (e.ctrlKey && e.altKey && (e.key === 'ArrowLeft' || e.key === 'ArrowRight')) {
|
|
982
991
|
e.preventDefault();
|
|
983
|
-
|
|
992
|
+
fwd(e);
|
|
993
|
+
}
|
|
994
|
+
// Its own branch: the Alt+digit case below requires !ctrlKey.
|
|
995
|
+
if (e.ctrlKey && e.altKey && !e.shiftKey && !e.metaKey && e.key.toLowerCase() === 'p') {
|
|
996
|
+
e.preventDefault();
|
|
997
|
+
fwd(e);
|
|
984
998
|
}
|
|
985
999
|
if (e.altKey && !e.ctrlKey && !e.shiftKey && !e.metaKey && /^[1-9]$/.test(e.key)) {
|
|
986
1000
|
e.preventDefault();
|
|
987
|
-
|
|
1001
|
+
fwd(e);
|
|
988
1002
|
}
|
|
989
1003
|
});
|
|
990
1004
|
})();
|
|
@@ -994,10 +1008,12 @@ function _hubNavigate(app, url) {
|
|
|
994
1008
|
window.parent?.postMessage({ type: 'hub:navigate', app, url }, '*');
|
|
995
1009
|
}
|
|
996
1010
|
|
|
1011
|
+
// Hoisted out of initHubTheme so initHubProject can share it.
|
|
1012
|
+
const hubOrigin = () => (window.__HUB__?.url ? new URL(window.__HUB__.url).origin : null);
|
|
1013
|
+
|
|
997
1014
|
(function initHubTheme() {
|
|
998
1015
|
const getTheme = () => (document.body.classList.contains('light') ? 'light' : 'dark');
|
|
999
1016
|
const getColorTheme = () => document.body.dataset.colorTheme || 'ember';
|
|
1000
|
-
const hubOrigin = () => (window.__HUB__?.url ? new URL(window.__HUB__.url).origin : null);
|
|
1001
1017
|
// lastTheme/lastColorTheme are updated synchronously when applying a hub
|
|
1002
1018
|
// message, so the (async) observer sees no diff and doesn't echo it back.
|
|
1003
1019
|
let lastTheme = getTheme();
|
|
@@ -1028,6 +1044,32 @@ function _hubNavigate(app, url) {
|
|
|
1028
1044
|
});
|
|
1029
1045
|
})();
|
|
1030
1046
|
|
|
1047
|
+
// Set synchronously when the hub pushes a project, so the DOMContentLoaded restore below can't
|
|
1048
|
+
// let a stale localStorage recent win the race against the hub's choice.
|
|
1049
|
+
let hubProjectPath = null;
|
|
1050
|
+
|
|
1051
|
+
(function initHubProject() {
|
|
1052
|
+
let lastApplied = null;
|
|
1053
|
+
window.addEventListener('message', async (e) => {
|
|
1054
|
+
if (e.source !== window.parent || e.origin !== hubOrigin()) return;
|
|
1055
|
+
if (e.data?.type !== 'hub:project') return;
|
|
1056
|
+
const dirPath = e.data.project;
|
|
1057
|
+
if (typeof dirPath !== 'string' || !dirPath) return;
|
|
1058
|
+
hubProjectPath = dirPath;
|
|
1059
|
+
// Dedupes against the last applied value, not just an in-flight one: the hub re-posts on
|
|
1060
|
+
// every iframe load, so without this each load would PUT and reload twice.
|
|
1061
|
+
if (lastApplied === dirPath) return;
|
|
1062
|
+
lastApplied = dirPath;
|
|
1063
|
+
try {
|
|
1064
|
+
await putProject(dirPath);
|
|
1065
|
+
await Promise.all([loadProject(), loadData()]);
|
|
1066
|
+
} catch (err) {
|
|
1067
|
+
lastApplied = null;
|
|
1068
|
+
console.warn('hub:project failed:', err.message);
|
|
1069
|
+
}
|
|
1070
|
+
});
|
|
1071
|
+
})();
|
|
1072
|
+
|
|
1031
1073
|
// #endregion HUB_INTEGRATION
|
|
1032
1074
|
|
|
1033
1075
|
// #region RESIZE
|
|
@@ -1086,16 +1128,21 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|
|
1086
1128
|
// Handle ?project= query param, else restore last-used project from localStorage
|
|
1087
1129
|
const params = new URLSearchParams(location.search);
|
|
1088
1130
|
let desiredProject = params.get('project');
|
|
1089
|
-
|
|
1131
|
+
// A hub-pushed project outranks the localStorage recent: initHub() is fire-and-forget at script
|
|
1132
|
+
// eval, so hub:project can land before or during this block.
|
|
1133
|
+
if (!desiredProject) desiredProject = hubProjectPath || getRecentProjects()[0] || null;
|
|
1090
1134
|
if (desiredProject) {
|
|
1091
1135
|
try {
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1136
|
+
await putProject(desiredProject);
|
|
1137
|
+
} catch {
|
|
1138
|
+
if (params.has('project')) showToast('Failed to switch project', 'error');
|
|
1139
|
+
}
|
|
1140
|
+
// A push that arrived while the PUT above was in flight must still win.
|
|
1141
|
+
if (hubProjectPath && hubProjectPath !== desiredProject) {
|
|
1142
|
+
try {
|
|
1143
|
+
await putProject(hubProjectPath);
|
|
1144
|
+
} catch {}
|
|
1145
|
+
}
|
|
1099
1146
|
}
|
|
1100
1147
|
if (params.has('project')) {
|
|
1101
1148
|
params.delete('project');
|
package/public/style.css
CHANGED
|
@@ -202,8 +202,11 @@ body {
|
|
|
202
202
|
background: var(--bg-hover);
|
|
203
203
|
color: var(--text-primary);
|
|
204
204
|
}
|
|
205
|
+
/* Highlighted with the row's *own* theme accent rather than the active theme's, so the selected
|
|
206
|
+
row previews the theme it names. --sw-accent comes from the theme-swatch-<id> class on the row. */
|
|
205
207
|
.theme-menu-item.on {
|
|
206
|
-
color: var(--accent-text);
|
|
208
|
+
color: var(--sw-accent, var(--accent-text));
|
|
209
|
+
background: color-mix(in srgb, var(--sw-accent, var(--accent-text)) 16%, transparent);
|
|
207
210
|
}
|
|
208
211
|
/* 3-dot palette preview: surface, accent, ink (colors from themes.css vars) */
|
|
209
212
|
.theme-swatch {
|
package/server.js
CHANGED
|
@@ -21,7 +21,7 @@ const expandHome = (p) => (p || '').replace(/^~/, os.homedir());
|
|
|
21
21
|
|
|
22
22
|
const PORT = getArg('port') || process.env.PORT || 3544;
|
|
23
23
|
const AUTO_OPEN = process.argv.includes('--open');
|
|
24
|
-
const claudeDirArg = getArg('dir');
|
|
24
|
+
const claudeDirArg = getArg('dir') || process.env.CLAUDE_CONFIG_DIR || process.env.CLAUDE_DIR;
|
|
25
25
|
const CLAUDE_DIR = claudeDirArg ? expandHome(claudeDirArg) : path.join(os.homedir(), '.claude');
|
|
26
26
|
const projectDirArg = getArg('project');
|
|
27
27
|
|
|
@@ -334,7 +334,7 @@ function discoverMemorySources(projectPath) {
|
|
|
334
334
|
// project: <project>/.claude/agent-memory/<agent>/
|
|
335
335
|
// local: <project>/.claude/agent-memory-local/<agent>/
|
|
336
336
|
const agentMemoryRoots = [
|
|
337
|
-
{ root: path.join(
|
|
337
|
+
{ root: path.join(CLAUDE_DIR, 'agent-memory'), agentScope: 'user' },
|
|
338
338
|
{ root: path.join(projectPath, '.claude', 'agent-memory'), agentScope: 'project' },
|
|
339
339
|
{ root: path.join(projectPath, '.claude', 'agent-memory-local'), agentScope: 'local' },
|
|
340
340
|
];
|