claude-code-memory-explorer 0.5.1 → 0.7.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 CHANGED
@@ -4,7 +4,11 @@
4
4
  [![license](https://img.shields.io/npm/l/claude-code-memory-explorer)](LICENSE)
5
5
  [![npm downloads](https://img.shields.io/npm/dm/claude-code-memory-explorer)](https://www.npmjs.com/package/claude-code-memory-explorer)
6
6
 
7
- > See everything Claude Code knows about your project — CLAUDE.md files, rules, auto memory, and imports.
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
+ ![Memory explorer overview](assets/shot-overview.png)
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
- Open http://localhost:3544 (or use `--open` to auto-launch the browser).
16
-
17
- That's it. No config — the dashboard reads your existing Claude Code memory files.
18
-
19
- ![Dark theme](assets/main-dark.png)
20
- ![Light theme](assets/main-light.png)
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** — User CLAUDE.md, project CLAUDE.md, CLAUDE.local.md, rules, auto memory, and managed policies
25
- - **Import resolution** — Follows `@path/to/file.md` references and `[text](file.md)` markdown links up to 5 levels deep
26
- - **Rules inspection** — Shows path-scoped frontmatter (`paths`, `type`, `name`) with conditional load indicators
27
- - **Auto memory** — Visualizes MEMORY.md with startup cutoff line, on-demand topic files, and frontmatter badges
28
- - **Tree + preview** — Left panel grouped by scope (user/project/rules/memory), right panel with syntax-highlighted preview
29
- - **Keyboard-driven** — j/k navigation, h/l group jump, e to open in editor, t for theme, ? for help
30
- - **Resizable sidebar** — Drag handle between tree and preview, width persisted
31
- - **Browser history** — Back/forward navigates file selection, bookmarkable URLs
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
- ## Configuration
32
+ ![Path-scoped rule with frontmatter badges](assets/shot-rules.png)
36
33
 
37
- ```bash
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
+ ![Auto memory index with topic files](assets/shot-auto-memory.png)
43
35
 
44
- If port 3544 is in use, the server falls back to a random available port.
36
+ ![Dark theme — Tokyo Night](assets/shot-dark-theme.png)
45
37
 
46
- ### Global install
38
+ ## Configuration
47
39
 
48
- ```bash
49
- npm install -g claude-code-memory-explorer
50
- claude-code-memory-explorer --open
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
- ## How It Works
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-memory-explorer",
3
- "version": "0.5.1",
3
+ "version": "0.7.0",
4
4
  "description": "Memory explorer dashboard for Claude Code",
5
5
  "main": "server.js",
6
6
  "bin": {
package/public/app.js CHANGED
@@ -92,11 +92,74 @@ function restorePlaceholders(html, placeholders) {
92
92
 
93
93
  // #region THEME
94
94
 
95
+ const COLOR_THEMES = [
96
+ ['ember', 'Ember'],
97
+ ['gruvbox', 'Gruvbox'],
98
+ ['catppuccin', 'Catppuccin'],
99
+ ['tokyo-night', 'Tokyo Night'],
100
+ ['solarized', 'Solarized'],
101
+ ['dracula', 'Dracula'],
102
+ ['nord', 'Nord'],
103
+ ['rose-pine', 'Rosé Pine'],
104
+ ['everforest', 'Everforest'],
105
+ ['kanagawa', 'Kanagawa'],
106
+ ['one-dark', 'One Dark'],
107
+ ['night-owl', 'Night Owl'],
108
+ ['monokai', 'Monokai Pro'],
109
+ ['github', 'GitHub'],
110
+ ['ayu', 'Ayu'],
111
+ ['vitesse', 'Vitesse'],
112
+ ['synthwave', "Synthwave '84"],
113
+ ];
114
+
95
115
  function loadTheme() {
96
116
  if (localStorage.getItem('theme') === 'light') document.body.classList.add('light');
117
+ buildThemeMenu();
118
+ const colorTheme = localStorage.getItem('color-theme');
119
+ if (colorTheme) document.body.dataset.colorTheme = colorTheme;
120
+ syncColorThemeMenu(colorTheme || 'ember');
97
121
  syncHljsTheme();
98
122
  }
99
123
 
124
+ // 'ember' (the :root default) has no override block — selecting it clears the attribute.
125
+ function setColorTheme(id) {
126
+ if (!id || id === 'ember') {
127
+ delete document.body.dataset.colorTheme;
128
+ localStorage.removeItem('color-theme');
129
+ } else {
130
+ document.body.dataset.colorTheme = id;
131
+ localStorage.setItem('color-theme', id);
132
+ }
133
+ syncColorThemeMenu(id);
134
+ }
135
+
136
+ function buildThemeMenu() {
137
+ const menu = document.getElementById('themeMenu');
138
+ menu.innerHTML = COLOR_THEMES.map(
139
+ ([id, label]) =>
140
+ `<button type="button" class="theme-menu-item" data-theme-id="${id}"
141
+ onclick="event.stopPropagation(); setColorTheme('${id}'); toggleThemeMenu()">
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
+ </button>`,
144
+ ).join('');
145
+ }
146
+
147
+ // biome-ignore lint/correctness/noUnusedVariables: called from topbar markup
148
+ function toggleThemeMenu(e) {
149
+ e?.stopPropagation();
150
+ const menu = document.getElementById('themeMenu');
151
+ const open = menu.classList.toggle('open');
152
+ if (open) {
153
+ document.addEventListener('click', () => menu.classList.remove('open'), { once: true });
154
+ }
155
+ }
156
+
157
+ function syncColorThemeMenu(id) {
158
+ document.querySelectorAll('.theme-menu-item').forEach((el) => {
159
+ el.classList.toggle('on', el.dataset.themeId === (id || 'ember'));
160
+ });
161
+ }
162
+
100
163
  function toggleTheme() {
101
164
  document.body.classList.toggle('light');
102
165
  localStorage.setItem('theme', document.body.classList.contains('light') ? 'light' : 'dark');
@@ -933,22 +996,36 @@ function _hubNavigate(app, url) {
933
996
 
934
997
  (function initHubTheme() {
935
998
  const getTheme = () => (document.body.classList.contains('light') ? 'light' : 'dark');
999
+ const getColorTheme = () => document.body.dataset.colorTheme || 'ember';
936
1000
  const hubOrigin = () => (window.__HUB__?.url ? new URL(window.__HUB__.url).origin : null);
1001
+ // lastTheme/lastColorTheme are updated synchronously when applying a hub
1002
+ // message, so the (async) observer sees no diff and doesn't echo it back.
937
1003
  let lastTheme = getTheme();
1004
+ let lastColorTheme = getColorTheme();
938
1005
  window.addEventListener('message', (e) => {
939
1006
  if (e.source !== window.parent || e.origin !== hubOrigin()) return;
940
1007
  if (e.data?.type !== 'hub:theme') return;
941
- if (getTheme() === e.data.theme) return;
942
- window.toggleTheme();
943
- lastTheme = getTheme();
1008
+ if (typeof e.data.colorTheme === 'string' && e.data.colorTheme !== getColorTheme()) {
1009
+ setColorTheme(e.data.colorTheme);
1010
+ lastColorTheme = getColorTheme();
1011
+ }
1012
+ if (getTheme() !== e.data.theme) {
1013
+ window.toggleTheme();
1014
+ lastTheme = getTheme();
1015
+ }
944
1016
  });
945
1017
  new MutationObserver(() => {
946
1018
  const t = getTheme();
947
- if (t === lastTheme) return;
1019
+ const ct = getColorTheme();
1020
+ if (t === lastTheme && ct === lastColorTheme) return;
948
1021
  lastTheme = t;
1022
+ lastColorTheme = ct;
949
1023
  const origin = hubOrigin();
950
- if (origin) window.parent.postMessage({ type: 'hub:theme', theme: t }, origin);
951
- }).observe(document.body, { attributes: true, attributeFilter: ['class'] });
1024
+ if (origin) window.parent.postMessage({ type: 'hub:theme', theme: t, colorTheme: ct }, origin);
1025
+ }).observe(document.body, {
1026
+ attributes: true,
1027
+ attributeFilter: ['class', 'data-color-theme'],
1028
+ });
952
1029
  })();
953
1030
 
954
1031
  // #endregion HUB_INTEGRATION
package/public/index.html CHANGED
@@ -8,6 +8,7 @@
8
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
9
  <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=Playfair+Display:wght@400;500;600&display=swap" rel="stylesheet">
10
10
  <link rel="stylesheet" href="style.css">
11
+ <link rel="stylesheet" href="themes.css">
11
12
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/github-dark.min.css" id="hljsDark">
12
13
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/github.min.css" id="hljsLight" disabled>
13
14
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js"></script>
@@ -34,6 +35,10 @@
34
35
  <button class="topbar-btn" id="refreshBtn" title="Refresh (r)" onclick="refreshData()">
35
36
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 11-2.12-9.36L23 10"/></svg>
36
37
  </button>
38
+ <span class="topbar-btn theme-picker" id="themePickerBtn" title="Color theme" onclick="toggleThemeMenu(event)">
39
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="13.5" cy="6.5" r=".5" fill="currentColor"/><circle cx="17.5" cy="10.5" r=".5" fill="currentColor"/><circle cx="8.5" cy="7.5" r=".5" fill="currentColor"/><circle cx="6.5" cy="12.5" r=".5" fill="currentColor"/><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2z"/></svg>
40
+ <div class="theme-menu" id="themeMenu"></div>
41
+ </span>
37
42
  <button class="topbar-btn" id="themeBtn" title="Toggle theme (t)" onclick="toggleTheme()">
38
43
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>
39
44
  </button>
package/public/style.css CHANGED
@@ -158,6 +158,75 @@ body {
158
158
  .topbar-btn.loading svg {
159
159
  animation: spin 1s linear infinite;
160
160
  }
161
+ /* theme picker: palette icon button + custom popover with per-theme swatches
162
+ (swatch colors are generated into themes.css) */
163
+ .theme-picker {
164
+ position: relative;
165
+ }
166
+ .theme-menu {
167
+ display: none;
168
+ position: absolute;
169
+ top: calc(100% + 6px);
170
+ right: 0;
171
+ z-index: 100;
172
+ min-width: 170px;
173
+ max-height: 70vh;
174
+ overflow-y: auto;
175
+ padding: 4px;
176
+ background: var(--bg-elevated);
177
+ border: 1px solid var(--border);
178
+ border-radius: 8px;
179
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
180
+ }
181
+ .theme-menu.open {
182
+ display: block;
183
+ }
184
+ .theme-menu-item {
185
+ display: flex;
186
+ align-items: center;
187
+ gap: 8px;
188
+ width: 100%;
189
+ padding: 6px 8px;
190
+ border: 0;
191
+ border-radius: 6px;
192
+ background: none;
193
+ color: var(--text-secondary);
194
+ font-family: var(--font-mono);
195
+ font-size: 11px;
196
+ text-transform: uppercase;
197
+ letter-spacing: 0.03em;
198
+ text-align: left;
199
+ cursor: pointer;
200
+ }
201
+ .theme-menu-item:hover {
202
+ background: var(--bg-hover);
203
+ color: var(--text-primary);
204
+ }
205
+ .theme-menu-item.on {
206
+ color: var(--accent-text);
207
+ }
208
+ /* 3-dot palette preview: surface, accent, ink (colors from themes.css vars) */
209
+ .theme-swatch {
210
+ display: inline-flex;
211
+ align-items: center;
212
+ gap: 3px;
213
+ flex: 0 0 auto;
214
+ }
215
+ .theme-swatch i {
216
+ width: 9px;
217
+ height: 9px;
218
+ border-radius: 50%;
219
+ border: 1px solid var(--sw-border);
220
+ }
221
+ .theme-swatch .sw-bg {
222
+ background: var(--sw-bg);
223
+ }
224
+ .theme-swatch .sw-accent {
225
+ background: var(--sw-accent);
226
+ }
227
+ .theme-swatch .sw-ink {
228
+ background: var(--sw-ink);
229
+ }
161
230
  /* #endregion TOPBAR */
162
231
 
163
232
  /* #region BUDGET_BAR */
@@ -0,0 +1,551 @@
1
+ /* GENERATED by scripts/generate-themes.mjs — do not edit by hand.
2
+ Color themes override only the shared design-token variables; semantic
3
+ colors (success/warning/error/scope/...) stay constant across themes. */
4
+
5
+ /* picker swatches */
6
+ .theme-swatch-ember { --sw-bg: #16181c; --sw-accent: #e86f33; --sw-ink: #f0f1f3; --sw-border: #363840; }
7
+ body.light .theme-swatch-ember { --sw-bg: #efede9; --sw-accent: #e86f33; --sw-ink: #0a0a0a; --sw-border: #cfcbc4; }
8
+ .theme-swatch-gruvbox { --sw-bg: #282828; --sw-accent: #fe8019; --sw-ink: #ebdbb2; --sw-border: #504945; }
9
+ body.light .theme-swatch-gruvbox { --sw-bg: #f3eee0; --sw-accent: #d65d0e; --sw-ink: #3c3836; --sw-border: #c9bc9d; }
10
+ .theme-swatch-catppuccin { --sw-bg: #181825; --sw-accent: #cba6f7; --sw-ink: #cdd6f4; --sw-border: #45475a; }
11
+ body.light .theme-swatch-catppuccin { --sw-bg: #eff1f5; --sw-accent: #8839ef; --sw-ink: #4c4f69; --sw-border: #bcc0cc; }
12
+ .theme-swatch-tokyo-night { --sw-bg: #1a1b26; --sw-accent: #7aa2f7; --sw-ink: #c0caf5; --sw-border: #3b4261; }
13
+ body.light .theme-swatch-tokyo-night { --sw-bg: #e9eaf0; --sw-accent: #2e7de9; --sw-ink: #343b58; --sw-border: #a8aecb; }
14
+ .theme-swatch-solarized { --sw-bg: #073642; --sw-accent: #cb4b16; --sw-ink: #93a1a1; --sw-border: #586e75; }
15
+ body.light .theme-swatch-solarized { --sw-bg: #f5efdc; --sw-accent: #cb4b16; --sw-ink: #073642; --sw-border: #d3cbb7; }
16
+ .theme-swatch-dracula { --sw-bg: #282a36; --sw-accent: #bd93f9; --sw-ink: #f8f8f2; --sw-border: #565a75; }
17
+ body.light .theme-swatch-dracula { --sw-bg: #f0f0f5; --sw-accent: #7c3aed; --sw-ink: #16161e; --sw-border: #c9c9d6; }
18
+ .theme-swatch-nord { --sw-bg: #2e3440; --sw-accent: #88c0d0; --sw-ink: #eceff4; --sw-border: #4c566a; }
19
+ body.light .theme-swatch-nord { --sw-bg: #eceff4; --sw-accent: #5e81ac; --sw-ink: #2e3440; --sw-border: #c2c9d6; }
20
+ .theme-swatch-rose-pine { --sw-bg: #1f1d2e; --sw-accent: #c4a7e7; --sw-ink: #e0def4; --sw-border: #403d52; }
21
+ body.light .theme-swatch-rose-pine { --sw-bg: #faf4ed; --sw-accent: #907aa9; --sw-ink: #575279; --sw-border: #dfdad9; }
22
+ .theme-swatch-everforest { --sw-bg: #2d353b; --sw-accent: #a7c080; --sw-ink: #d3c6aa; --sw-border: #475258; }
23
+ body.light .theme-swatch-everforest { --sw-bg: #fdf6e3; --sw-accent: #6f8352; --sw-ink: #4d5960; --sw-border: #d8d3ba; }
24
+ .theme-swatch-kanagawa { --sw-bg: #1f1f28; --sw-accent: #7e9cd8; --sw-ink: #dcd7ba; --sw-border: #54546d; }
25
+ body.light .theme-swatch-kanagawa { --sw-bg: #f2ecbc; --sw-accent: #4d699b; --sw-ink: #545464; --sw-border: #c7bf94; }
26
+ .theme-swatch-one-dark { --sw-bg: #282c34; --sw-accent: #61afef; --sw-ink: #d7dae0; --sw-border: #4b5263; }
27
+ body.light .theme-swatch-one-dark { --sw-bg: #fafafa; --sw-accent: #4078f2; --sw-ink: #383a42; --sw-border: #d4d4d6; }
28
+ .theme-swatch-night-owl { --sw-bg: #0b2942; --sw-accent: #82aaff; --sw-ink: #d6deeb; --sw-border: #5f7e97; }
29
+ body.light .theme-swatch-night-owl { --sw-bg: #f6f6f6; --sw-accent: #0c969b; --sw-ink: #403f53; --sw-border: #d0d0d0; }
30
+ .theme-swatch-monokai { --sw-bg: #2d2a2e; --sw-accent: #ffd866; --sw-ink: #fcfcfa; --sw-border: #5b595c; }
31
+ body.light .theme-swatch-monokai { --sw-bg: #f1efed; --sw-accent: #c08a00; --sw-ink: #2c292d; --sw-border: #cdc8c5; }
32
+ .theme-swatch-github { --sw-bg: #24292e; --sw-accent: #58a6ff; --sw-ink: #e1e4e8; --sw-border: #444d56; }
33
+ body.light .theme-swatch-github { --sw-bg: #f6f8fa; --sw-accent: #0366d6; --sw-ink: #24292e; --sw-border: #d1d5da; }
34
+ .theme-swatch-ayu { --sw-bg: #10141c; --sw-accent: #e6b450; --sw-ink: #bfbdb6; --sw-border: #2d3343; }
35
+ body.light .theme-swatch-ayu { --sw-bg: #f8f9fa; --sw-accent: #f2ae49; --sw-ink: #3d4149; --sw-border: #d8dde2; }
36
+ .theme-swatch-vitesse { --sw-bg: #181818; --sw-accent: #4d9375; --sw-ink: #dbd7ca; --sw-border: #333333; }
37
+ body.light .theme-swatch-vitesse { --sw-bg: #f7f7f7; --sw-accent: #1c6b48; --sw-ink: #393a34; --sw-border: #d6d6d6; }
38
+ .theme-swatch-synthwave { --sw-bg: #262335; --sw-accent: #ff7edb; --sw-ink: #ffffff; --sw-border: #495495; }
39
+ body.light .theme-swatch-synthwave { --sw-bg: #efecf6; --sw-accent: #c936a6; --sw-ink: #241b2f; --sw-border: #c5bcdd; }
40
+
41
+ /* Gruvbox */
42
+ body[data-color-theme="gruvbox"]:not(.light) {
43
+ --accent: #fe8019;
44
+ --accent-text: #f9b27c;
45
+ --accent-dim: rgba(254,128,25,0.25);
46
+ --accent-glow: rgba(254, 128, 25, 0.55);
47
+ --bg-deep: #1d2021;
48
+ --bg-surface: #282828;
49
+ --bg-elevated: #32302f;
50
+ --bg-hover: #3c3836;
51
+ --border: #504945;
52
+ --text-primary: #ebdbb2;
53
+ --text-secondary: #d5c4a1;
54
+ --text-tertiary: #bdae93;
55
+ --text-muted: #928374;
56
+ }
57
+ body.light[data-color-theme="gruvbox"] {
58
+ --accent: #d65d0e;
59
+ --accent-text: #af3a03;
60
+ --accent-dim: rgba(214,93,14,0.18);
61
+ --accent-glow: rgba(214, 93, 14, 0.5);
62
+ --bg-deep: #ede7d5;
63
+ --bg-surface: #f3eee0;
64
+ --bg-elevated: #faf7ec;
65
+ --bg-hover: #e4dcc6;
66
+ --border: #c9bc9d;
67
+ --text-primary: #3c3836;
68
+ --text-secondary: #504945;
69
+ --text-tertiary: #665c54;
70
+ --text-muted: #7c6f64;
71
+ }
72
+
73
+ /* Catppuccin */
74
+ body[data-color-theme="catppuccin"]:not(.light) {
75
+ --accent: #cba6f7;
76
+ --accent-text: #d8c2fa;
77
+ --accent-dim: rgba(203,166,247,0.25);
78
+ --accent-glow: rgba(203, 166, 247, 0.55);
79
+ --bg-deep: #11111b;
80
+ --bg-surface: #181825;
81
+ --bg-elevated: #1e1e2e;
82
+ --bg-hover: #313244;
83
+ --border: #45475a;
84
+ --text-primary: #cdd6f4;
85
+ --text-secondary: #bac2de;
86
+ --text-tertiary: #a6adc8;
87
+ --text-muted: #7f849c;
88
+ }
89
+ body.light[data-color-theme="catppuccin"] {
90
+ --accent: #8839ef;
91
+ --accent-text: #6f2dbd;
92
+ --accent-dim: rgba(136,57,239,0.18);
93
+ --accent-glow: rgba(136, 57, 239, 0.5);
94
+ --bg-deep: #e6e9ef;
95
+ --bg-surface: #eff1f5;
96
+ --bg-elevated: #ffffff;
97
+ --bg-hover: #ccd0da;
98
+ --border: #bcc0cc;
99
+ --text-primary: #4c4f69;
100
+ --text-secondary: #5c5f77;
101
+ --text-tertiary: #6c6f85;
102
+ --text-muted: #8c8fa1;
103
+ }
104
+
105
+ /* Tokyo Night */
106
+ body[data-color-theme="tokyo-night"]:not(.light) {
107
+ --accent: #7aa2f7;
108
+ --accent-text: #9ab8ff;
109
+ --accent-dim: rgba(122,162,247,0.25);
110
+ --accent-glow: rgba(122, 162, 247, 0.55);
111
+ --bg-deep: #16161e;
112
+ --bg-surface: #1a1b26;
113
+ --bg-elevated: #1f2335;
114
+ --bg-hover: #292e42;
115
+ --border: #3b4261;
116
+ --text-primary: #c0caf5;
117
+ --text-secondary: #a9b1d6;
118
+ --text-tertiary: #787c99;
119
+ --text-muted: #565f89;
120
+ }
121
+ body.light[data-color-theme="tokyo-night"] {
122
+ --accent: #2e7de9;
123
+ --accent-text: #1659c7;
124
+ --accent-dim: rgba(46,125,233,0.18);
125
+ --accent-glow: rgba(46, 125, 233, 0.5);
126
+ --bg-deep: #e1e2e7;
127
+ --bg-surface: #e9eaf0;
128
+ --bg-elevated: #f7f8fc;
129
+ --bg-hover: #d0d5e3;
130
+ --border: #a8aecb;
131
+ --text-primary: #343b58;
132
+ --text-secondary: #565a6e;
133
+ --text-tertiary: #6c6e75;
134
+ --text-muted: #848cb5;
135
+ }
136
+
137
+ /* Solarized */
138
+ body[data-color-theme="solarized"]:not(.light) {
139
+ --accent: #cb4b16;
140
+ --accent-text: #e9663a;
141
+ --accent-dim: rgba(203,75,22,0.25);
142
+ --accent-glow: rgba(203, 75, 22, 0.55);
143
+ --bg-deep: #002b36;
144
+ --bg-surface: #073642;
145
+ --bg-elevated: #0a4250;
146
+ --bg-hover: #11505f;
147
+ --border: #586e75;
148
+ --text-primary: #93a1a1;
149
+ --text-secondary: #839496;
150
+ --text-tertiary: #657b83;
151
+ --text-muted: #586e75;
152
+ }
153
+ body.light[data-color-theme="solarized"] {
154
+ --accent: #cb4b16;
155
+ --accent-text: #b34a12;
156
+ --accent-dim: rgba(203,75,22,0.18);
157
+ --accent-glow: rgba(203, 75, 22, 0.5);
158
+ --bg-deep: #eee8d5;
159
+ --bg-surface: #f5efdc;
160
+ --bg-elevated: #fdf6e3;
161
+ --bg-hover: #e4ddc8;
162
+ --border: #d3cbb7;
163
+ --text-primary: #073642;
164
+ --text-secondary: #586e75;
165
+ --text-tertiary: #657b83;
166
+ --text-muted: #839496;
167
+ }
168
+
169
+ /* Dracula */
170
+ body[data-color-theme="dracula"]:not(.light) {
171
+ --accent: #bd93f9;
172
+ --accent-text: #d6b5ff;
173
+ --accent-dim: rgba(189,147,249,0.25);
174
+ --accent-glow: rgba(189, 147, 249, 0.55);
175
+ --bg-deep: #21222c;
176
+ --bg-surface: #282a36;
177
+ --bg-elevated: #343746;
178
+ --bg-hover: #44475a;
179
+ --border: #565a75;
180
+ --text-primary: #f8f8f2;
181
+ --text-secondary: #d8d8d2;
182
+ --text-tertiary: #b0b3c5;
183
+ --text-muted: #6272a4;
184
+ }
185
+ body.light[data-color-theme="dracula"] {
186
+ --accent: #7c3aed;
187
+ --accent-text: #5b21b6;
188
+ --accent-dim: rgba(124,58,237,0.18);
189
+ --accent-glow: rgba(124, 58, 237, 0.5);
190
+ --bg-deep: #e8e8ee;
191
+ --bg-surface: #f0f0f5;
192
+ --bg-elevated: #fbfbfd;
193
+ --bg-hover: #dfdfe8;
194
+ --border: #c9c9d6;
195
+ --text-primary: #16161e;
196
+ --text-secondary: #34343f;
197
+ --text-tertiary: #5a5a68;
198
+ --text-muted: #787885;
199
+ }
200
+
201
+ /* Nord */
202
+ body[data-color-theme="nord"]:not(.light) {
203
+ --accent: #88c0d0;
204
+ --accent-text: #a8d8e8;
205
+ --accent-dim: rgba(136,192,208,0.25);
206
+ --accent-glow: rgba(136, 192, 208, 0.55);
207
+ --bg-deep: #272c36;
208
+ --bg-surface: #2e3440;
209
+ --bg-elevated: #3b4252;
210
+ --bg-hover: #434c5e;
211
+ --border: #4c566a;
212
+ --text-primary: #eceff4;
213
+ --text-secondary: #d8dee9;
214
+ --text-tertiary: #aeb6c5;
215
+ --text-muted: #7b88a1;
216
+ }
217
+ body.light[data-color-theme="nord"] {
218
+ --accent: #5e81ac;
219
+ --accent-text: #44688f;
220
+ --accent-dim: rgba(94,129,172,0.18);
221
+ --accent-glow: rgba(94, 129, 172, 0.5);
222
+ --bg-deep: #e5e9f0;
223
+ --bg-surface: #eceff4;
224
+ --bg-elevated: #f8f9fb;
225
+ --bg-hover: #d8dee9;
226
+ --border: #c2c9d6;
227
+ --text-primary: #2e3440;
228
+ --text-secondary: #3b4252;
229
+ --text-tertiary: #4c566a;
230
+ --text-muted: #6b7589;
231
+ }
232
+
233
+ /* Rosé Pine */
234
+ body[data-color-theme="rose-pine"]:not(.light) {
235
+ --accent: #c4a7e7;
236
+ --accent-text: #d9c4f2;
237
+ --accent-dim: rgba(196,167,231,0.25);
238
+ --accent-glow: rgba(196, 167, 231, 0.55);
239
+ --bg-deep: #191724;
240
+ --bg-surface: #1f1d2e;
241
+ --bg-elevated: #26233a;
242
+ --bg-hover: #34304e;
243
+ --border: #403d52;
244
+ --text-primary: #e0def4;
245
+ --text-secondary: #c5c2dd;
246
+ --text-tertiary: #908caa;
247
+ --text-muted: #6e6a86;
248
+ }
249
+ body.light[data-color-theme="rose-pine"] {
250
+ --accent: #907aa9;
251
+ --accent-text: #6f598c;
252
+ --accent-dim: rgba(144,122,169,0.18);
253
+ --accent-glow: rgba(144, 122, 169, 0.5);
254
+ --bg-deep: #f2e9e1;
255
+ --bg-surface: #faf4ed;
256
+ --bg-elevated: #fffaf3;
257
+ --bg-hover: #ebdfd4;
258
+ --border: #dfdad9;
259
+ --text-primary: #575279;
260
+ --text-secondary: #635e87;
261
+ --text-tertiary: #797593;
262
+ --text-muted: #9893a5;
263
+ }
264
+
265
+ /* Everforest */
266
+ body[data-color-theme="everforest"]:not(.light) {
267
+ --accent: #a7c080;
268
+ --accent-text: #c3d6a2;
269
+ --accent-dim: rgba(167,192,128,0.25);
270
+ --accent-glow: rgba(167, 192, 128, 0.55);
271
+ --bg-deep: #232a2e;
272
+ --bg-surface: #2d353b;
273
+ --bg-elevated: #343f44;
274
+ --bg-hover: #3d484d;
275
+ --border: #475258;
276
+ --text-primary: #d3c6aa;
277
+ --text-secondary: #bfb6a3;
278
+ --text-tertiary: #9da9a0;
279
+ --text-muted: #859289;
280
+ }
281
+ body.light[data-color-theme="everforest"] {
282
+ --accent: #6f8352;
283
+ --accent-text: #56683f;
284
+ --accent-dim: rgba(111,131,82,0.18);
285
+ --accent-glow: rgba(111, 131, 82, 0.5);
286
+ --bg-deep: #f4f0d9;
287
+ --bg-surface: #fdf6e3;
288
+ --bg-elevated: #fffbef;
289
+ --bg-hover: #efebd4;
290
+ --border: #d8d3ba;
291
+ --text-primary: #4d5960;
292
+ --text-secondary: #5c6a72;
293
+ --text-tertiary: #7a8478;
294
+ --text-muted: #939f91;
295
+ }
296
+
297
+ /* Kanagawa */
298
+ body[data-color-theme="kanagawa"]:not(.light) {
299
+ --accent: #7e9cd8;
300
+ --accent-text: #9fb9ec;
301
+ --accent-dim: rgba(126,156,216,0.25);
302
+ --accent-glow: rgba(126, 156, 216, 0.55);
303
+ --bg-deep: #16161d;
304
+ --bg-surface: #1f1f28;
305
+ --bg-elevated: #2a2a37;
306
+ --bg-hover: #363646;
307
+ --border: #54546d;
308
+ --text-primary: #dcd7ba;
309
+ --text-secondary: #c8c093;
310
+ --text-tertiary: #a6a69c;
311
+ --text-muted: #727169;
312
+ }
313
+ body.light[data-color-theme="kanagawa"] {
314
+ --accent: #4d699b;
315
+ --accent-text: #38537f;
316
+ --accent-dim: rgba(77,105,155,0.18);
317
+ --accent-glow: rgba(77, 105, 155, 0.5);
318
+ --bg-deep: #e5ddb0;
319
+ --bg-surface: #f2ecbc;
320
+ --bg-elevated: #faf5d2;
321
+ --bg-hover: #dcd5ac;
322
+ --border: #c7bf94;
323
+ --text-primary: #545464;
324
+ --text-secondary: #66667a;
325
+ --text-tertiary: #716e61;
326
+ --text-muted: #8a8775;
327
+ }
328
+
329
+ /* One Dark */
330
+ body[data-color-theme="one-dark"]:not(.light) {
331
+ --accent: #61afef;
332
+ --accent-text: #8cc7ff;
333
+ --accent-dim: rgba(97,175,239,0.25);
334
+ --accent-glow: rgba(97, 175, 239, 0.55);
335
+ --bg-deep: #21252b;
336
+ --bg-surface: #282c34;
337
+ --bg-elevated: #2f343e;
338
+ --bg-hover: #3e4452;
339
+ --border: #4b5263;
340
+ --text-primary: #d7dae0;
341
+ --text-secondary: #abb2bf;
342
+ --text-tertiary: #8b919e;
343
+ --text-muted: #5c6370;
344
+ }
345
+ body.light[data-color-theme="one-dark"] {
346
+ --accent: #4078f2;
347
+ --accent-text: #2c5dd4;
348
+ --accent-dim: rgba(64,120,242,0.18);
349
+ --accent-glow: rgba(64, 120, 242, 0.5);
350
+ --bg-deep: #eaeaeb;
351
+ --bg-surface: #fafafa;
352
+ --bg-elevated: #ffffff;
353
+ --bg-hover: #e0e0e2;
354
+ --border: #d4d4d6;
355
+ --text-primary: #383a42;
356
+ --text-secondary: #50525a;
357
+ --text-tertiary: #696c77;
358
+ --text-muted: #a0a1a7;
359
+ }
360
+
361
+ /* Night Owl */
362
+ body[data-color-theme="night-owl"]:not(.light) {
363
+ --accent: #82aaff;
364
+ --accent-text: #a8c4ff;
365
+ --accent-dim: rgba(130,170,255,0.25);
366
+ --accent-glow: rgba(130, 170, 255, 0.55);
367
+ --bg-deep: #011627;
368
+ --bg-surface: #0b2942;
369
+ --bg-elevated: #13344f;
370
+ --bg-hover: #1d3b53;
371
+ --border: #5f7e97;
372
+ --text-primary: #d6deeb;
373
+ --text-secondary: #b8c5d6;
374
+ --text-tertiary: #8ba3b8;
375
+ --text-muted: #637777;
376
+ }
377
+ body.light[data-color-theme="night-owl"] {
378
+ --accent: #0c969b;
379
+ --accent-text: #0a7479;
380
+ --accent-dim: rgba(12,150,155,0.18);
381
+ --accent-glow: rgba(12, 150, 155, 0.5);
382
+ --bg-deep: #ededed;
383
+ --bg-surface: #f6f6f6;
384
+ --bg-elevated: #fbfbfb;
385
+ --bg-hover: #e2e2e2;
386
+ --border: #d0d0d0;
387
+ --text-primary: #403f53;
388
+ --text-secondary: #545167;
389
+ --text-tertiary: #6f6e85;
390
+ --text-muted: #989fb1;
391
+ }
392
+
393
+ /* Monokai Pro */
394
+ body[data-color-theme="monokai"]:not(.light) {
395
+ --accent: #ffd866;
396
+ --accent-text: #ffe6a0;
397
+ --accent-dim: rgba(255,216,102,0.25);
398
+ --accent-glow: rgba(255, 216, 102, 0.55);
399
+ --bg-deep: #221f22;
400
+ --bg-surface: #2d2a2e;
401
+ --bg-elevated: #403e41;
402
+ --bg-hover: #4a484b;
403
+ --border: #5b595c;
404
+ --text-primary: #fcfcfa;
405
+ --text-secondary: #d9d8d6;
406
+ --text-tertiary: #939293;
407
+ --text-muted: #727072;
408
+ }
409
+ body.light[data-color-theme="monokai"] {
410
+ --accent: #c08a00;
411
+ --accent-text: #9a6e00;
412
+ --accent-dim: rgba(192,138,0,0.18);
413
+ --accent-glow: rgba(192, 138, 0, 0.5);
414
+ --bg-deep: #e9e6e4;
415
+ --bg-surface: #f1efed;
416
+ --bg-elevated: #fcfbfa;
417
+ --bg-hover: #e0dcda;
418
+ --border: #cdc8c5;
419
+ --text-primary: #2c292d;
420
+ --text-secondary: #46434a;
421
+ --text-tertiary: #6b686d;
422
+ --text-muted: #8d8a8d;
423
+ }
424
+
425
+ /* GitHub */
426
+ body[data-color-theme="github"]:not(.light) {
427
+ --accent: #58a6ff;
428
+ --accent-text: #85bdff;
429
+ --accent-dim: rgba(88,166,255,0.25);
430
+ --accent-glow: rgba(88, 166, 255, 0.55);
431
+ --bg-deep: #1f2428;
432
+ --bg-surface: #24292e;
433
+ --bg-elevated: #2b3138;
434
+ --bg-hover: #2f363d;
435
+ --border: #444d56;
436
+ --text-primary: #e1e4e8;
437
+ --text-secondary: #d1d5da;
438
+ --text-tertiary: #959da5;
439
+ --text-muted: #6a737d;
440
+ }
441
+ body.light[data-color-theme="github"] {
442
+ --accent: #0366d6;
443
+ --accent-text: #044289;
444
+ --accent-dim: rgba(3,102,214,0.18);
445
+ --accent-glow: rgba(3, 102, 214, 0.5);
446
+ --bg-deep: #f0f2f4;
447
+ --bg-surface: #f6f8fa;
448
+ --bg-elevated: #ffffff;
449
+ --bg-hover: #e8eaed;
450
+ --border: #d1d5da;
451
+ --text-primary: #24292e;
452
+ --text-secondary: #444d56;
453
+ --text-tertiary: #586069;
454
+ --text-muted: #6a737d;
455
+ }
456
+
457
+ /* Ayu */
458
+ body[data-color-theme="ayu"]:not(.light) {
459
+ --accent: #e6b450;
460
+ --accent-text: #f0cd85;
461
+ --accent-dim: rgba(230,180,80,0.25);
462
+ --accent-glow: rgba(230, 180, 80, 0.55);
463
+ --bg-deep: #0d1017;
464
+ --bg-surface: #10141c;
465
+ --bg-elevated: #141821;
466
+ --bg-hover: #1d2330;
467
+ --border: #2d3343;
468
+ --text-primary: #bfbdb6;
469
+ --text-secondary: #a8a6a0;
470
+ --text-tertiary: #8a9199;
471
+ --text-muted: #6c7380;
472
+ }
473
+ body.light[data-color-theme="ayu"] {
474
+ --accent: #f2ae49;
475
+ --accent-text: #b87514;
476
+ --accent-dim: rgba(242,174,73,0.18);
477
+ --accent-glow: rgba(242, 174, 73, 0.5);
478
+ --bg-deep: #eff1f3;
479
+ --bg-surface: #f8f9fa;
480
+ --bg-elevated: #fcfcfc;
481
+ --bg-hover: #e7eaed;
482
+ --border: #d8dde2;
483
+ --text-primary: #3d4149;
484
+ --text-secondary: #5c6166;
485
+ --text-tertiary: #787b80;
486
+ --text-muted: #8a9199;
487
+ }
488
+
489
+ /* Vitesse */
490
+ body[data-color-theme="vitesse"]:not(.light) {
491
+ --accent: #4d9375;
492
+ --accent-text: #6fb392;
493
+ --accent-dim: rgba(77,147,117,0.25);
494
+ --accent-glow: rgba(77, 147, 117, 0.55);
495
+ --bg-deep: #121212;
496
+ --bg-surface: #181818;
497
+ --bg-elevated: #1e1e1e;
498
+ --bg-hover: #262626;
499
+ --border: #333333;
500
+ --text-primary: #dbd7ca;
501
+ --text-secondary: #bfbaaa;
502
+ --text-tertiary: #8f8f85;
503
+ --text-muted: #758575;
504
+ }
505
+ body.light[data-color-theme="vitesse"] {
506
+ --accent: #1c6b48;
507
+ --accent-text: #145236;
508
+ --accent-dim: rgba(28,107,72,0.18);
509
+ --accent-glow: rgba(28, 107, 72, 0.5);
510
+ --bg-deep: #f0f0f0;
511
+ --bg-surface: #f7f7f7;
512
+ --bg-elevated: #ffffff;
513
+ --bg-hover: #e7e7e7;
514
+ --border: #d6d6d6;
515
+ --text-primary: #393a34;
516
+ --text-secondary: #4e4f47;
517
+ --text-tertiary: #6b6d63;
518
+ --text-muted: #a0ada0;
519
+ }
520
+
521
+ /* Synthwave '84 */
522
+ body[data-color-theme="synthwave"]:not(.light) {
523
+ --accent: #ff7edb;
524
+ --accent-text: #ffa9e7;
525
+ --accent-dim: rgba(255,126,219,0.25);
526
+ --accent-glow: rgba(255, 126, 219, 0.55);
527
+ --bg-deep: #241b2f;
528
+ --bg-surface: #262335;
529
+ --bg-elevated: #322a47;
530
+ --bg-hover: #3d3460;
531
+ --border: #495495;
532
+ --text-primary: #ffffff;
533
+ --text-secondary: #d8d6e8;
534
+ --text-tertiary: #b6b1d8;
535
+ --text-muted: #848bbd;
536
+ }
537
+ body.light[data-color-theme="synthwave"] {
538
+ --accent: #c936a6;
539
+ --accent-text: #9c2381;
540
+ --accent-dim: rgba(201,54,166,0.18);
541
+ --accent-glow: rgba(201, 54, 166, 0.5);
542
+ --bg-deep: #e6e2f0;
543
+ --bg-surface: #efecf6;
544
+ --bg-elevated: #fbfafd;
545
+ --bg-hover: #ddd7ec;
546
+ --border: #c5bcdd;
547
+ --text-primary: #241b2f;
548
+ --text-secondary: #3d3455;
549
+ --text-tertiary: #5c5380;
550
+ --text-muted: #7d76a3;
551
+ }
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(os.homedir(), '.claude', 'agent-memory'), agentScope: 'user' },
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
  ];