claude-code-memory-explorer 0.16.0 → 1.0.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/hub-app.json +25 -0
- package/lib/net-guard.js +6 -1
- package/package.json +2 -1
- package/public/app.js +52 -103
- package/public/index.html +2 -0
- package/public/style.css +23 -6
- package/public/themes.css +32 -0
- package/public/vendor/claude-hub-sdk.js +50 -0
- package/server.js +2 -10
package/hub-app.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"manifest": 1,
|
|
3
|
+
"id": "memory",
|
|
4
|
+
"name": "Memory Diagnoser",
|
|
5
|
+
"icon": "database",
|
|
6
|
+
"run": { "entry": "server.js", "defaultPort": 3544 },
|
|
7
|
+
"loading": {
|
|
8
|
+
"verbs": [
|
|
9
|
+
"Recalling memories…",
|
|
10
|
+
"Walking the tree…",
|
|
11
|
+
"Dusting off notes…",
|
|
12
|
+
"Reading CLAUDE.md…",
|
|
13
|
+
"Following imports…",
|
|
14
|
+
"Tracing the context…",
|
|
15
|
+
"Flipping through pages…",
|
|
16
|
+
"Connecting the dots…",
|
|
17
|
+
"Checking what sticks…"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
"actions": {
|
|
21
|
+
"handles": {
|
|
22
|
+
"project.memory": { "params": { "project": "string?" }, "url": "?project={project}", "mode": "message" }
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
package/lib/net-guard.js
CHANGED
|
@@ -36,6 +36,7 @@ const PORT_RE = /^[0-9]{1,5}$/;
|
|
|
36
36
|
// The port is validated but not compared — the listen(0) fallbacks make the real
|
|
37
37
|
// port dynamic, so only the host is allowlisted.
|
|
38
38
|
const validPort = (p) => PORT_RE.test(p) && Number(p) >= 1 && Number(p) <= 65535;
|
|
39
|
+
const KEEP_ALIVE_MS = 65000;
|
|
39
40
|
|
|
40
41
|
function isLoopbackAddress(host) {
|
|
41
42
|
if (!host) return false;
|
|
@@ -249,6 +250,10 @@ function createNetGuard(config = {}) {
|
|
|
249
250
|
const serverOpts = opts.maxHeaderSize ? { maxHeaderSize: opts.maxHeaderSize } : {};
|
|
250
251
|
const makeServer = (handler) => {
|
|
251
252
|
const s = http.createServer(serverOpts, handler);
|
|
253
|
+
// Node's 5 s default closes idle sockets that the hub proxy and the browser would reuse, and a
|
|
254
|
+
// fresh loopback connect is the one that some Windows network filters drop. The hub's agent
|
|
255
|
+
// drops its idle sockets before this, so it never reuses one the server is closing.
|
|
256
|
+
s.keepAliveTimeout = KEEP_ALIVE_MS;
|
|
252
257
|
if (opts.onUpgrade) s.on('upgrade', opts.onUpgrade);
|
|
253
258
|
return s;
|
|
254
259
|
};
|
|
@@ -281,4 +286,4 @@ function createNetGuard(config = {}) {
|
|
|
281
286
|
return { BIND_HOST, ALLOWED_HOSTS, EXPOSED, hostGuard, originGuard, upgradeVerdict, frameGuard, listenLoopback, exposureWarning };
|
|
282
287
|
}
|
|
283
288
|
|
|
284
|
-
module.exports = { createNetGuard, parseHostHeader, isLoopbackAddress };
|
|
289
|
+
module.exports = { createNetGuard, parseHostHeader, isLoopbackAddress, KEEP_ALIVE_MS };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-memory-explorer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Memory explorer dashboard for Claude Code",
|
|
5
5
|
"main": "server.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"license": "MIT",
|
|
47
47
|
"files": [
|
|
48
48
|
"server.js",
|
|
49
|
+
"hub-app.json",
|
|
49
50
|
"lib/",
|
|
50
51
|
"public/",
|
|
51
52
|
"README.md",
|
package/public/app.js
CHANGED
|
@@ -145,15 +145,17 @@ function setColorTheme(id) {
|
|
|
145
145
|
syncColorThemeMenu(id);
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
function buildThemeMenu() {
|
|
148
|
+
function buildThemeMenu(themes = COLOR_THEMES) {
|
|
149
149
|
const menu = document.getElementById('themeMenu');
|
|
150
|
-
menu.innerHTML =
|
|
151
|
-
(
|
|
152
|
-
|
|
150
|
+
menu.innerHTML = themes
|
|
151
|
+
.map(
|
|
152
|
+
([id, label]) =>
|
|
153
|
+
`<button type="button" class="theme-menu-item theme-swatch-${id}" data-theme-id="${id}"
|
|
153
154
|
onclick="event.stopPropagation(); setColorTheme('${id}'); toggleThemeMenu()">
|
|
154
|
-
<span class="theme-swatch theme-swatch-${id}"><i class="sw-bg"></i><i class="sw-accent"></i><i class="sw-ink"></i></span>${label}
|
|
155
|
+
<span class="theme-swatch theme-swatch-${id}"><i class="sw-bg"></i><i class="sw-accent"></i><i class="sw-ink"></i></span>${esc(label)}
|
|
155
156
|
</button>`,
|
|
156
|
-
|
|
157
|
+
)
|
|
158
|
+
.join('');
|
|
157
159
|
}
|
|
158
160
|
|
|
159
161
|
// biome-ignore lint/correctness/noUnusedVariables: called from topbar markup
|
|
@@ -453,6 +455,8 @@ const SHORTCUT_PAIRS = [
|
|
|
453
455
|
{ keys: ['Ctrl', 'Alt', '←/→'], combo: true, label: 'Previous / next hub app' },
|
|
454
456
|
{ keys: ['Alt', '1…9'], combo: true, label: 'Jump to hub app by number' },
|
|
455
457
|
{ keys: ['Ctrl', 'Alt', 'P'], combo: true, label: 'Project picker' },
|
|
458
|
+
{ keys: ['Ctrl', 'Alt', 'W'], combo: true, label: 'Config dir picker' },
|
|
459
|
+
{ keys: ['Ctrl', 'Alt', 'A'], combo: true, label: 'App launcher' },
|
|
456
460
|
],
|
|
457
461
|
},
|
|
458
462
|
],
|
|
@@ -490,7 +494,7 @@ function buildHelpShortcuts() {
|
|
|
490
494
|
function toggleHelpModal() {
|
|
491
495
|
const list = document.getElementById('helpShortcuts');
|
|
492
496
|
if (!list.childElementCount) list.innerHTML = buildHelpShortcuts();
|
|
493
|
-
list.classList.toggle('sc-standalone',
|
|
497
|
+
list.classList.toggle('sc-standalone', hub.status === 'standalone');
|
|
494
498
|
document.getElementById('helpModal').classList.toggle('open');
|
|
495
499
|
}
|
|
496
500
|
|
|
@@ -2137,117 +2141,47 @@ document.addEventListener('keydown', (e) => {
|
|
|
2137
2141
|
|
|
2138
2142
|
// #region HUB_INTEGRATION
|
|
2139
2143
|
|
|
2140
|
-
|
|
2141
|
-
const cfg = await fetch('/hub-config')
|
|
2142
|
-
.then((r) => r.json())
|
|
2143
|
-
.catch(() => ({}));
|
|
2144
|
-
if (!cfg.enabled) return;
|
|
2145
|
-
window.__HUB__ = cfg;
|
|
2146
|
-
// e.code travels with e.key because macOS composes Option+<key> into a character (Option+P is
|
|
2147
|
-
// 'π'), so the key alone cannot identify the binding. The hub owns the keymap and normalizes;
|
|
2148
|
-
// these tests only decide whether a press is the hub's to handle.
|
|
2149
|
-
document.addEventListener('keydown', (e) => {
|
|
2150
|
-
if (!isHubKey(e)) return;
|
|
2151
|
-
e.preventDefault();
|
|
2152
|
-
hubPost({ type: 'hub:keydown', key: e.key, code: e.code, ctrl: e.ctrlKey, alt: e.altKey, shift: e.shiftKey });
|
|
2153
|
-
});
|
|
2154
|
-
})();
|
|
2155
|
-
|
|
2156
|
-
// The combos the hub binds, from its hub:keys message. Null until one arrives: a hub from before
|
|
2157
|
-
// hub:keys sends none, and the fallback filter below is what such a hub expects.
|
|
2158
|
-
let hubKeys = null;
|
|
2159
|
-
|
|
2160
|
-
// A copy of the hub's comboOf(): its names must match the hub:keys list.
|
|
2161
|
-
function hubCombo(e) {
|
|
2162
|
-
const lower = (e.key || '').toLowerCase();
|
|
2163
|
-
const m = /^(?:Key|Digit)([A-Z1-9])$/.exec(e.code || '');
|
|
2164
|
-
const key = /^[a-z1-9]$/.test(lower) ? lower : m ? m[1].toLowerCase() : e.key;
|
|
2165
|
-
const mods = [e.ctrlKey && 'ctrl', e.altKey && 'alt', e.shiftKey && 'shift', e.metaKey && 'meta'];
|
|
2166
|
-
return [...mods, key].filter(Boolean).join('+');
|
|
2167
|
-
}
|
|
2168
|
-
|
|
2169
|
-
function isHubKey(e) {
|
|
2170
|
-
if (hubKeys) return hubKeys.has(hubCombo(e));
|
|
2171
|
-
if (e.ctrlKey && e.altKey && (e.key === 'ArrowLeft' || e.key === 'ArrowRight')) return true;
|
|
2172
|
-
// Own branch: the Alt+digit case below requires !ctrlKey. The hub owns the Ctrl+Alt+letter
|
|
2173
|
-
// keymap and ignores unbound letters.
|
|
2174
|
-
if (e.ctrlKey && e.altKey && !e.shiftKey && !e.metaKey && (/^[a-z]$/i.test(e.key) || /^Key[A-Z]$/.test(e.code))) {
|
|
2175
|
-
return true;
|
|
2176
|
-
}
|
|
2177
|
-
return e.altKey && !e.ctrlKey && !e.shiftKey && !e.metaKey && (/^[1-9]$/.test(e.key) || /^Digit[1-9]$/.test(e.code));
|
|
2178
|
-
}
|
|
2179
|
-
|
|
2180
|
-
function _hubNavigate(app, url) {
|
|
2181
|
-
if (!window.__HUB__?.enabled) return;
|
|
2182
|
-
hubPost({ type: 'hub:navigate', app, url });
|
|
2183
|
-
}
|
|
2184
|
-
|
|
2185
|
-
// Hoisted out of initHubTheme so initHubProject can share it.
|
|
2186
|
-
const hubOrigin = () => (window.__HUB__?.url ? new URL(window.__HUB__.url).origin : null);
|
|
2144
|
+
const hub = ClaudeHub.connect();
|
|
2187
2145
|
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
if (origin) window.parent?.postMessage(message, origin);
|
|
2193
|
-
}
|
|
2146
|
+
document.getElementById('helpDocs')?.addEventListener('click', (e) => {
|
|
2147
|
+
e.preventDefault();
|
|
2148
|
+
hub.openExternal(e.currentTarget.href);
|
|
2149
|
+
});
|
|
2194
2150
|
|
|
2195
2151
|
(function initHubTheme() {
|
|
2196
2152
|
const getTheme = () => (document.body.classList.contains('light') ? 'light' : 'dark');
|
|
2197
2153
|
const getColorTheme = () => document.body.dataset.colorTheme || 'ember';
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
if (e.data?.type !== 'hub:theme') return;
|
|
2205
|
-
if (typeof e.data.colorTheme === 'string' && e.data.colorTheme !== getColorTheme()) {
|
|
2206
|
-
setColorTheme(e.data.colorTheme);
|
|
2207
|
-
lastColorTheme = getColorTheme();
|
|
2208
|
-
}
|
|
2209
|
-
if (getTheme() !== e.data.theme) {
|
|
2210
|
-
window.toggleTheme();
|
|
2211
|
-
lastTheme = getTheme();
|
|
2212
|
-
}
|
|
2154
|
+
const report = hub.bindTheme({
|
|
2155
|
+
get: () => ({ theme: getTheme(), colorTheme: getColorTheme() }),
|
|
2156
|
+
set: ({ theme, colorTheme }) => {
|
|
2157
|
+
if (colorTheme !== getColorTheme()) setColorTheme(colorTheme);
|
|
2158
|
+
if (theme !== getTheme()) window.toggleTheme();
|
|
2159
|
+
},
|
|
2213
2160
|
});
|
|
2214
|
-
new MutationObserver((
|
|
2215
|
-
const t = getTheme();
|
|
2216
|
-
const ct = getColorTheme();
|
|
2217
|
-
if (t === lastTheme && ct === lastColorTheme) return;
|
|
2218
|
-
lastTheme = t;
|
|
2219
|
-
lastColorTheme = ct;
|
|
2220
|
-
hubPost({ type: 'hub:theme', theme: t, colorTheme: ct });
|
|
2221
|
-
}).observe(document.body, {
|
|
2161
|
+
new MutationObserver(report).observe(document.body, {
|
|
2222
2162
|
attributes: true,
|
|
2223
2163
|
attributeFilter: ['class', 'data-color-theme'],
|
|
2224
2164
|
});
|
|
2225
2165
|
})();
|
|
2226
2166
|
|
|
2227
|
-
(
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
});
|
|
2233
|
-
})();
|
|
2167
|
+
// Under the hub the picker lists the hub's themes, which include the user's own (protocol section 6, rule 5).
|
|
2168
|
+
hub.onThemes((themes) => {
|
|
2169
|
+
buildThemeMenu(themes.map((t) => [t.id, t.label]));
|
|
2170
|
+
syncColorThemeMenu(document.body.dataset.colorTheme);
|
|
2171
|
+
});
|
|
2234
2172
|
|
|
2235
2173
|
// Set synchronously when the hub pushes a project, so the DOMContentLoaded restore below can't
|
|
2236
2174
|
// let a stale localStorage recent win the race against the hub's choice.
|
|
2237
2175
|
let hubProjectPath = null;
|
|
2176
|
+
// Shared with the DOMContentLoaded restore so a boot restore and a hub push never apply the same project twice.
|
|
2177
|
+
let lastAppliedProject = null;
|
|
2238
2178
|
|
|
2239
2179
|
(function initHubProject() {
|
|
2240
|
-
|
|
2241
|
-
window.addEventListener('message', async (e) => {
|
|
2242
|
-
if (e.source !== window.parent || e.origin !== hubOrigin()) return;
|
|
2243
|
-
if (e.data?.type !== 'hub:project') return;
|
|
2244
|
-
const dirPath = e.data.project;
|
|
2245
|
-
if (typeof dirPath !== 'string' || !dirPath) return;
|
|
2246
|
-
hubProjectPath = dirPath;
|
|
2180
|
+
async function applyProject(dirPath) {
|
|
2247
2181
|
// Dedupes against the last applied value, not just an in-flight one: the hub re-posts on
|
|
2248
2182
|
// every iframe load, so without this each load would PUT and reload twice.
|
|
2249
|
-
if (
|
|
2250
|
-
|
|
2183
|
+
if (lastAppliedProject === dirPath) return;
|
|
2184
|
+
lastAppliedProject = dirPath;
|
|
2251
2185
|
try {
|
|
2252
2186
|
await putProject(dirPath);
|
|
2253
2187
|
// The hub does not persist its own scope, so a hub-pushed project must become the recent
|
|
@@ -2256,9 +2190,22 @@ let hubProjectPath = null;
|
|
|
2256
2190
|
addRecentProject(dirPath);
|
|
2257
2191
|
await Promise.all([loadProject(), loadData()]);
|
|
2258
2192
|
} catch (err) {
|
|
2259
|
-
|
|
2260
|
-
|
|
2193
|
+
lastAppliedProject = null;
|
|
2194
|
+
throw err;
|
|
2261
2195
|
}
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2198
|
+
hub.subscribe('project.changed', (p) => {
|
|
2199
|
+
const dirPath = typeof p?.project === 'string' && p.project ? p.project : null;
|
|
2200
|
+
if (!dirPath) return;
|
|
2201
|
+
hubProjectPath = dirPath;
|
|
2202
|
+
applyProject(dirPath).catch((err) => console.warn('project.changed failed:', err.message));
|
|
2203
|
+
});
|
|
2204
|
+
|
|
2205
|
+
// A link moves this app off the hub's project, and applyProject records that, so the next
|
|
2206
|
+
// project.changed still applies.
|
|
2207
|
+
hub.handle('project.memory', (p) => {
|
|
2208
|
+
if (p.project) applyProject(p.project).catch(() => showToast('Failed to switch project', 'error'));
|
|
2262
2209
|
});
|
|
2263
2210
|
})();
|
|
2264
2211
|
|
|
@@ -2320,11 +2267,13 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|
|
2320
2267
|
// Handle ?project= query param, else restore last-used project from localStorage
|
|
2321
2268
|
const params = new URLSearchParams(location.search);
|
|
2322
2269
|
let desiredProject = params.get('project');
|
|
2323
|
-
// A hub-pushed project outranks the localStorage recent:
|
|
2324
|
-
// eval, so
|
|
2270
|
+
// A hub-pushed project outranks the localStorage recent: ClaudeHub.connect() is fire-and-forget at script
|
|
2271
|
+
// eval, so project.changed can land before or during this block.
|
|
2325
2272
|
await projectInfoPromise;
|
|
2326
2273
|
if (!desiredProject) desiredProject = hubProjectPath || getRecentProjects()[0] || null;
|
|
2327
2274
|
if (desiredProject) {
|
|
2275
|
+
// Set before the PUT: the hub's push can land while it is in flight.
|
|
2276
|
+
lastAppliedProject = desiredProject;
|
|
2328
2277
|
try {
|
|
2329
2278
|
await putProject(desiredProject);
|
|
2330
2279
|
} catch {
|
package/public/index.html
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
<script defer src="/app.js"></script>
|
|
28
28
|
</head>
|
|
29
29
|
<body>
|
|
30
|
+
<script src="/vendor/claude-hub-sdk.js"></script>
|
|
30
31
|
<div class="topbar">
|
|
31
32
|
<div class="topbar-logo">
|
|
32
33
|
<div class="logo-mark"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v4c0 1.66 4.03 3 9 3s9-1.34 9-3V5"/><path d="M3 9v4c0 1.66 4.03 3 9 3s9-1.34 9-3V9"/><path d="M3 13v4c0 1.66 4.03 3 9 3s9-1.34 9-3v-4"/></svg></div>
|
|
@@ -166,6 +167,7 @@
|
|
|
166
167
|
<div class="modal modal-help">
|
|
167
168
|
<div class="modal-header">
|
|
168
169
|
<h3>Memory Diagnoser</h3>
|
|
170
|
+
<a class="help-docs" id="helpDocs" href="https://nikiforovall.blog/claude-code-memory/" target="_blank" rel="noopener">Docs ↗</a>
|
|
169
171
|
<button class="modal-close" onclick="toggleHelpModal()">✕</button>
|
|
170
172
|
</div>
|
|
171
173
|
<div class="modal-body">
|
package/public/style.css
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
--text-muted: #7d808a;
|
|
12
12
|
--accent: #e86f33;
|
|
13
13
|
--accent-text: #f0a070;
|
|
14
|
-
--accent-dim: rgba(232, 111, 51, 0.
|
|
14
|
+
--accent-dim: rgba(232, 111, 51, 0.25);
|
|
15
15
|
--accent-glow: rgba(232, 111, 51, 0.55);
|
|
16
16
|
--success: #3ecf8e;
|
|
17
17
|
--success-dim: rgba(62, 207, 142, 0.18);
|
|
@@ -964,6 +964,23 @@ body.light .tree-conditional .file-name {
|
|
|
964
964
|
margin-bottom: 6px;
|
|
965
965
|
text-transform: uppercase;
|
|
966
966
|
}
|
|
967
|
+
.help-docs {
|
|
968
|
+
margin: 0 12px 0 auto;
|
|
969
|
+
padding: 4px 10px;
|
|
970
|
+
border: 1px solid var(--border);
|
|
971
|
+
border-radius: 6px;
|
|
972
|
+
color: var(--text-secondary);
|
|
973
|
+
font-size: 12px;
|
|
974
|
+
text-decoration: none;
|
|
975
|
+
white-space: nowrap;
|
|
976
|
+
transition:
|
|
977
|
+
color 0.15s ease,
|
|
978
|
+
border-color 0.15s ease;
|
|
979
|
+
}
|
|
980
|
+
.help-docs:hover {
|
|
981
|
+
color: var(--accent);
|
|
982
|
+
border-color: var(--accent);
|
|
983
|
+
}
|
|
967
984
|
/* One shared grid for both halves (key | label | spacer | key | label): each key
|
|
968
985
|
gutter is sized by its column's widest combo, and left/right rows share row
|
|
969
986
|
tracks, so paired groups line up — a shorter group just leaves empty rows.
|
|
@@ -1239,15 +1256,15 @@ body.light {
|
|
|
1239
1256
|
color-scheme: light;
|
|
1240
1257
|
--bg-deep: #e8e6e3;
|
|
1241
1258
|
--bg-surface: #efede9;
|
|
1242
|
-
--bg-elevated: #
|
|
1259
|
+
--bg-elevated: #fbfaf9;
|
|
1243
1260
|
--bg-hover: #d2d0cc;
|
|
1244
1261
|
--border: #cfcbc4;
|
|
1245
1262
|
--text-primary: #0a0a0a;
|
|
1246
|
-
--text-secondary: #
|
|
1247
|
-
--text-tertiary: #
|
|
1248
|
-
--text-muted: #
|
|
1263
|
+
--text-secondary: #2c2c2c;
|
|
1264
|
+
--text-tertiary: #565656;
|
|
1265
|
+
--text-muted: #767676;
|
|
1249
1266
|
--accent-text: #a0501c;
|
|
1250
|
-
--accent-dim: rgba(
|
|
1267
|
+
--accent-dim: rgba(184, 90, 32, 0.18);
|
|
1251
1268
|
--accent-glow: rgba(232, 111, 51, 0.5);
|
|
1252
1269
|
--success: #1a8a5a;
|
|
1253
1270
|
--shadow-1: 0 1px 2px rgba(0, 0, 0, 0.08), 0 1px 4px rgba(0, 0, 0, 0.05);
|
package/public/themes.css
CHANGED
|
@@ -38,6 +38,38 @@ body.light .theme-swatch-vitesse { --sw-bg: #f7f7f7; --sw-accent: #1c6b48; --sw-
|
|
|
38
38
|
.theme-swatch-synthwave { --sw-bg: #262335; --sw-accent: #ff7edb; --sw-ink: #ffffff; --sw-border: #495495; }
|
|
39
39
|
body.light .theme-swatch-synthwave { --sw-bg: #efecf6; --sw-accent: #c936a6; --sw-ink: #241b2f; --sw-border: #c5bcdd; }
|
|
40
40
|
|
|
41
|
+
/* Ember */
|
|
42
|
+
body:not([data-color-theme]):not(.light) {
|
|
43
|
+
--accent: #e86f33;
|
|
44
|
+
--accent-text: #f0a070;
|
|
45
|
+
--accent-dim: rgba(232,111,51,0.25);
|
|
46
|
+
--accent-glow: rgba(232, 111, 51, 0.55);
|
|
47
|
+
--bg-deep: #101114;
|
|
48
|
+
--bg-surface: #16181c;
|
|
49
|
+
--bg-elevated: #1e2025;
|
|
50
|
+
--bg-hover: #282a30;
|
|
51
|
+
--border: #363840;
|
|
52
|
+
--text-primary: #f0f1f3;
|
|
53
|
+
--text-secondary: #c2c4c9;
|
|
54
|
+
--text-tertiary: #9a9da5;
|
|
55
|
+
--text-muted: #7d808a;
|
|
56
|
+
}
|
|
57
|
+
body.light:not([data-color-theme]) {
|
|
58
|
+
--accent: #e86f33;
|
|
59
|
+
--accent-text: #a0501c;
|
|
60
|
+
--accent-dim: rgba(184,90,32,0.18);
|
|
61
|
+
--accent-glow: rgba(232, 111, 51, 0.5);
|
|
62
|
+
--bg-deep: #e8e6e3;
|
|
63
|
+
--bg-surface: #efede9;
|
|
64
|
+
--bg-elevated: #fbfaf9;
|
|
65
|
+
--bg-hover: #d2d0cc;
|
|
66
|
+
--border: #cfcbc4;
|
|
67
|
+
--text-primary: #0a0a0a;
|
|
68
|
+
--text-secondary: #2c2c2c;
|
|
69
|
+
--text-tertiary: #565656;
|
|
70
|
+
--text-muted: #767676;
|
|
71
|
+
}
|
|
72
|
+
|
|
41
73
|
/* Gruvbox */
|
|
42
74
|
body[data-color-theme="gruvbox"]:not(.light) {
|
|
43
75
|
--accent: #fe8019;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// claude-hub-sdk 1.1.0 (sha256 0fed14faa670). Copied by npm run sdk:sync in claude-code-hub. Do not edit.
|
|
2
|
+
// Claude Code Hub SDK stub: what an app serves when no hub runs it. Under a hub, the hub hands the
|
|
3
|
+
// app its real SDK (client.js) in place of this file. The API must match client.js; a test checks it.
|
|
4
|
+
((root) => {
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
function createClaudeHub(win) {
|
|
8
|
+
let hub = null;
|
|
9
|
+
const off = () => {};
|
|
10
|
+
|
|
11
|
+
function connect({ standalone = {} } = {}) {
|
|
12
|
+
if (hub) return hub;
|
|
13
|
+
const standaloneFn = (action) => {
|
|
14
|
+
const fn = (typeof standalone === 'function' ? standalone() : standalone)[action];
|
|
15
|
+
return typeof fn === 'function' ? fn : null;
|
|
16
|
+
};
|
|
17
|
+
hub = {
|
|
18
|
+
status: 'standalone',
|
|
19
|
+
themes: [],
|
|
20
|
+
inHub: false,
|
|
21
|
+
onStatus: () => off,
|
|
22
|
+
onActive: () => off,
|
|
23
|
+
onThemes: () => off,
|
|
24
|
+
subscribe: () => off,
|
|
25
|
+
bindTheme: () => off,
|
|
26
|
+
handle() {},
|
|
27
|
+
publish() {},
|
|
28
|
+
invoke(action, params = {}) {
|
|
29
|
+
const target = standaloneFn(action)?.(params);
|
|
30
|
+
if (!target) return Promise.resolve({ ok: false, reason: 'unhandled' });
|
|
31
|
+
win.open(target, '_blank', 'noopener');
|
|
32
|
+
return Promise.resolve({ ok: true, handledBy: 'standalone' });
|
|
33
|
+
},
|
|
34
|
+
can: (action) => !!standaloneFn(action),
|
|
35
|
+
forwards: () => false,
|
|
36
|
+
closeGuard() {},
|
|
37
|
+
openExternal(url) {
|
|
38
|
+
win.open(url, '_blank', 'noopener');
|
|
39
|
+
},
|
|
40
|
+
terminalToken: () => Promise.resolve(null),
|
|
41
|
+
};
|
|
42
|
+
return hub;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return { connect };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (typeof module === 'object' && module.exports) module.exports = { createClaudeHub };
|
|
49
|
+
else root.ClaudeHub = createClaudeHub(root);
|
|
50
|
+
})(typeof window !== 'undefined' ? window : globalThis);
|
package/server.js
CHANGED
|
@@ -608,18 +608,10 @@ app.use(net.frameGuard);
|
|
|
608
608
|
app.use(net.originGuard);
|
|
609
609
|
|
|
610
610
|
app.use(express.json());
|
|
611
|
+
// Under a hub, the hub hands over its SDK. Alone, public/vendor/claude-hub-sdk.js is a stub.
|
|
612
|
+
if (process.env.HUB_SDK_SERVER) require(process.env.HUB_SDK_SERVER).mount(app);
|
|
611
613
|
app.use(express.static(path.join(__dirname, 'public')));
|
|
612
614
|
|
|
613
|
-
app.get('/hub-config', (_req, res) => {
|
|
614
|
-
res.json({
|
|
615
|
-
name: 'Memory Diagnoser',
|
|
616
|
-
icon: 'brain',
|
|
617
|
-
description: 'Explore Claude Code memory sources',
|
|
618
|
-
enabled: !!process.env.CLAUDE_HUB,
|
|
619
|
-
url: process.env.HUB_URL || null,
|
|
620
|
-
});
|
|
621
|
-
});
|
|
622
|
-
|
|
623
615
|
app.get('/api/project', (_req, res) => {
|
|
624
616
|
res.json({ path: currentProjectPath, name: path.basename(currentProjectPath), configDir: CLAUDE_DIR });
|
|
625
617
|
});
|