claude-code-memory-explorer 0.15.0 → 0.16.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/lib/net-guard.js +1 -1
- package/lib/open-editor.js +5 -3
- package/package.json +2 -1
- package/public/app.js +35 -15
- package/server.js +10 -11
package/lib/net-guard.js
CHANGED
|
@@ -245,7 +245,7 @@ function createNetGuard(config = {}) {
|
|
|
245
245
|
* @param {{maxHeaderSize?: number, onUpgrade?: (req: any, socket: any, head: Buffer) => void}} [opts]
|
|
246
246
|
*/
|
|
247
247
|
function listenLoopback(app, port, onReady, opts = {}) {
|
|
248
|
-
const http = require('http');
|
|
248
|
+
const http = require('node:http');
|
|
249
249
|
const serverOpts = opts.maxHeaderSize ? { maxHeaderSize: opts.maxHeaderSize } : {};
|
|
250
250
|
const makeServer = (handler) => {
|
|
251
251
|
const s = http.createServer(serverOpts, handler);
|
package/lib/open-editor.js
CHANGED
|
@@ -21,9 +21,9 @@
|
|
|
21
21
|
// npm packages and cannot import from each other. Run scripts/sync-security-lib.sh
|
|
22
22
|
// after editing.
|
|
23
23
|
|
|
24
|
-
const { spawn } = require('child_process');
|
|
25
|
-
const path = require('path');
|
|
26
|
-
const fs = require('fs');
|
|
24
|
+
const { spawn } = require('node:child_process');
|
|
25
|
+
const path = require('node:path');
|
|
26
|
+
const fs = require('node:fs');
|
|
27
27
|
|
|
28
28
|
const IS_WIN = process.platform === 'win32';
|
|
29
29
|
|
|
@@ -37,6 +37,7 @@ const VSCODE_FAMILY = new Set([
|
|
|
37
37
|
// escape it. Inside "..." cmd treats & | < > ^ ( ) as literal, so only these
|
|
38
38
|
// matter: '"' ends the quoted run, '%' triggers variable expansion whose value
|
|
39
39
|
// could itself contain a quote. Neither is legal in an NTFS path anyway.
|
|
40
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: NUL is one of the rejected characters
|
|
40
41
|
const WIN_CMD_FORBIDDEN = /["%\r\n\u0000]/;
|
|
41
42
|
|
|
42
43
|
function fail(message, status) {
|
|
@@ -168,6 +169,7 @@ function resolveEditor() {
|
|
|
168
169
|
*/
|
|
169
170
|
function assertOpenTarget(value, label = 'path') {
|
|
170
171
|
if (typeof value !== 'string' || !value.trim()) throw fail(`${label} must be a non-empty string`, 400);
|
|
172
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: NUL is one of the rejected characters
|
|
171
173
|
if (/[\r\n\u0000]/.test(value)) throw fail(`${label} contains invalid characters`, 400);
|
|
172
174
|
const resolved = path.resolve(value);
|
|
173
175
|
if (!fs.existsSync(resolved)) throw fail(`${label} does not exist`, 404);
|
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -2147,24 +2147,36 @@ document.addEventListener('keydown', (e) => {
|
|
|
2147
2147
|
// 'π'), so the key alone cannot identify the binding. The hub owns the keymap and normalizes;
|
|
2148
2148
|
// these tests only decide whether a press is the hub's to handle.
|
|
2149
2149
|
document.addEventListener('keydown', (e) => {
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
};
|
|
2154
|
-
if (e.ctrlKey && e.altKey && (e.key === 'ArrowLeft' || e.key === 'ArrowRight')) {
|
|
2155
|
-
fwd();
|
|
2156
|
-
}
|
|
2157
|
-
// Own branch: the Alt+digit case below requires !ctrlKey. The hub owns the Ctrl+Alt+letter
|
|
2158
|
-
// keymap and ignores unbound letters.
|
|
2159
|
-
if (e.ctrlKey && e.altKey && !e.shiftKey && !e.metaKey && (/^[a-z]$/i.test(e.key) || /^Key[A-Z]$/.test(e.code))) {
|
|
2160
|
-
fwd();
|
|
2161
|
-
}
|
|
2162
|
-
if (e.altKey && !e.ctrlKey && !e.shiftKey && !e.metaKey && (/^[1-9]$/.test(e.key) || /^Digit[1-9]$/.test(e.code))) {
|
|
2163
|
-
fwd();
|
|
2164
|
-
}
|
|
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 });
|
|
2165
2153
|
});
|
|
2166
2154
|
})();
|
|
2167
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
|
+
|
|
2168
2180
|
function _hubNavigate(app, url) {
|
|
2169
2181
|
if (!window.__HUB__?.enabled) return;
|
|
2170
2182
|
hubPost({ type: 'hub:navigate', app, url });
|
|
@@ -2212,6 +2224,14 @@ function hubPost(message) {
|
|
|
2212
2224
|
});
|
|
2213
2225
|
})();
|
|
2214
2226
|
|
|
2227
|
+
(function initHubKeys() {
|
|
2228
|
+
window.addEventListener('message', (e) => {
|
|
2229
|
+
if (e.source !== window.parent || e.origin !== hubOrigin()) return;
|
|
2230
|
+
if (e.data?.type !== 'hub:keys' || !Array.isArray(e.data.keys)) return;
|
|
2231
|
+
hubKeys = new Set(e.data.keys.filter((k) => typeof k === 'string'));
|
|
2232
|
+
});
|
|
2233
|
+
})();
|
|
2234
|
+
|
|
2215
2235
|
// Set synchronously when the hub pushes a project, so the DOMContentLoaded restore below can't
|
|
2216
2236
|
// let a stale localStorage recent win the race against the hub's choice.
|
|
2217
2237
|
let hubProjectPath = null;
|
package/server.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
const express = require('express');
|
|
5
|
-
const path = require('path');
|
|
6
|
-
const fs = require('fs');
|
|
7
|
-
const os = require('os');
|
|
8
|
-
const { execFileSync, spawn } = require('child_process');
|
|
9
|
-
const crypto = require('crypto');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const fs = require('node:fs');
|
|
7
|
+
const os = require('node:os');
|
|
8
|
+
const { execFileSync, spawn } = require('node:child_process');
|
|
9
|
+
const crypto = require('node:crypto');
|
|
10
10
|
const { assertOpenTarget, openInEditor } = require('./lib/open-editor');
|
|
11
11
|
const { createNetGuard } = require('./lib/net-guard');
|
|
12
12
|
|
|
@@ -156,8 +156,7 @@ function parseImports(content) {
|
|
|
156
156
|
const softLinks = [];
|
|
157
157
|
// Match @path/to/file.ext — must contain / or \ to be a file path import
|
|
158
158
|
const re = /@(~?[\w./-]+\/[\w./-]+|~\/[\w./-]+)/g;
|
|
159
|
-
|
|
160
|
-
while ((m = re.exec(content)) !== null) {
|
|
159
|
+
for (const m of content.matchAll(re)) {
|
|
161
160
|
if (m[1].includes('.') && !m[1].includes('/')) continue;
|
|
162
161
|
// Skip npm scoped packages (e.g. @biomejs/biome) — require file extension in last segment
|
|
163
162
|
const lastSeg = m[1].split('/').pop();
|
|
@@ -166,12 +165,12 @@ function parseImports(content) {
|
|
|
166
165
|
}
|
|
167
166
|
// Also match standalone @filename.md references (no path separator needed)
|
|
168
167
|
const re2 = /(?:^|\s)@([\w-]+\.md)\b/gm;
|
|
169
|
-
|
|
168
|
+
for (const m of content.matchAll(re2)) {
|
|
170
169
|
if (!imports.includes(m[1])) imports.push(m[1]);
|
|
171
170
|
}
|
|
172
171
|
// Match markdown links [text](path.md) — soft references, don't change load type
|
|
173
172
|
const re3 = /\[.*?\]\(((?!https?:\/\/)[^)]+\.md)\)/g;
|
|
174
|
-
|
|
173
|
+
for (const m of content.matchAll(re3)) {
|
|
175
174
|
if (!imports.includes(m[1]) && !softLinks.includes(m[1])) softLinks.push(m[1]);
|
|
176
175
|
}
|
|
177
176
|
return { imports, softLinks };
|
|
@@ -216,7 +215,7 @@ function spreadImports(filePath, content) {
|
|
|
216
215
|
}
|
|
217
216
|
|
|
218
217
|
function hasPathsFilter(frontmatter) {
|
|
219
|
-
if (!frontmatter
|
|
218
|
+
if (!frontmatter?.paths) return false;
|
|
220
219
|
return Array.isArray(frontmatter.paths) ? frontmatter.paths.length > 0 : true;
|
|
221
220
|
}
|
|
222
221
|
|
|
@@ -1343,7 +1342,7 @@ app.get('/api/rules/match', (req, res) => {
|
|
|
1343
1342
|
if (!filePath) return res.status(400).json({ error: 'file query param required' });
|
|
1344
1343
|
const rules = getStack().filter(s => s.scope === 'rule');
|
|
1345
1344
|
const matched = rules.filter(r => {
|
|
1346
|
-
if (!r.frontmatter
|
|
1345
|
+
if (!r.frontmatter?.paths) return true;
|
|
1347
1346
|
const patterns = Array.isArray(r.frontmatter.paths) ? r.frontmatter.paths : [r.frontmatter.paths];
|
|
1348
1347
|
return micromatch.isMatch(filePath, patterns);
|
|
1349
1348
|
});
|