log-llm-config 1.0.19 → 1.0.22
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/dist/endpoint_client.js +75 -0
- package/dist/log_config_files.js +305 -27
- package/package.json +1 -1
package/dist/endpoint_client.js
CHANGED
|
@@ -194,6 +194,81 @@ export const postStartupPayload = async (endpointUrl, body, timeoutMs = 5000) =>
|
|
|
194
194
|
req.end();
|
|
195
195
|
});
|
|
196
196
|
};
|
|
197
|
+
/**
|
|
198
|
+
* PATCH request to endpoint (same transport as postStartupPayload: Node http/https,
|
|
199
|
+
* localhost → 127.0.0.1). Used for hook-request manifest update so it behaves like other endpoint calls.
|
|
200
|
+
*/
|
|
201
|
+
export const patchPayload = async (endpointUrl, body, timeoutMs = 10000) => {
|
|
202
|
+
const url = new URL(endpointUrl);
|
|
203
|
+
const isHttps = url.protocol === 'https:';
|
|
204
|
+
let hostname = url.hostname;
|
|
205
|
+
if (hostname === 'localhost' || hostname === '::1') {
|
|
206
|
+
hostname = '127.0.0.1';
|
|
207
|
+
}
|
|
208
|
+
const payload = JSON.stringify(body);
|
|
209
|
+
const requestOptions = {
|
|
210
|
+
hostname,
|
|
211
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
212
|
+
path: `${url.pathname}${url.search}`,
|
|
213
|
+
method: 'PATCH',
|
|
214
|
+
headers: {
|
|
215
|
+
'Content-Type': 'application/json',
|
|
216
|
+
'Content-Length': Buffer.byteLength(payload).toString(),
|
|
217
|
+
},
|
|
218
|
+
timeout: timeoutMs,
|
|
219
|
+
};
|
|
220
|
+
const transport = isHttps ? https.request : http.request;
|
|
221
|
+
return new Promise((resolve, reject) => {
|
|
222
|
+
let timeoutId = null;
|
|
223
|
+
let requestCompleted = false;
|
|
224
|
+
const cleanup = () => {
|
|
225
|
+
if (timeoutId) {
|
|
226
|
+
clearTimeout(timeoutId);
|
|
227
|
+
timeoutId = null;
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
const req = transport(requestOptions, (res) => {
|
|
231
|
+
let responseBody = '';
|
|
232
|
+
res.setEncoding('utf8');
|
|
233
|
+
res.on('data', (chunk) => {
|
|
234
|
+
responseBody += chunk;
|
|
235
|
+
});
|
|
236
|
+
res.on('end', () => {
|
|
237
|
+
requestCompleted = true;
|
|
238
|
+
cleanup();
|
|
239
|
+
if (!responseBody) {
|
|
240
|
+
resolve({ status: 'error', error: 'empty_response' });
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
try {
|
|
244
|
+
resolve(JSON.parse(responseBody));
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
reject(new Error(`Invalid JSON: ${responseBody.slice(0, 200)}`));
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
req.on('error', (error) => {
|
|
252
|
+
requestCompleted = true;
|
|
253
|
+
cleanup();
|
|
254
|
+
reject(error);
|
|
255
|
+
});
|
|
256
|
+
req.on('timeout', () => {
|
|
257
|
+
requestCompleted = true;
|
|
258
|
+
cleanup();
|
|
259
|
+
req.destroy();
|
|
260
|
+
reject(new Error(`Request timeout after ${timeoutMs}ms`));
|
|
261
|
+
});
|
|
262
|
+
timeoutId = setTimeout(() => {
|
|
263
|
+
if (!requestCompleted) {
|
|
264
|
+
req.destroy();
|
|
265
|
+
reject(new Error(`Request timeout after ${timeoutMs}ms`));
|
|
266
|
+
}
|
|
267
|
+
}, timeoutMs);
|
|
268
|
+
req.write(payload);
|
|
269
|
+
req.end();
|
|
270
|
+
});
|
|
271
|
+
};
|
|
197
272
|
export const classifyEndpointResponse = (response) => {
|
|
198
273
|
if (response.status === 'key_issued' && response.key) {
|
|
199
274
|
return {
|
package/dist/log_config_files.js
CHANGED
|
@@ -6,7 +6,7 @@ import { homedir } from 'node:os';
|
|
|
6
6
|
import crypto from 'node:crypto';
|
|
7
7
|
import { execSync } from 'node:child_process';
|
|
8
8
|
import path from 'node:path';
|
|
9
|
-
import { getFileCollectionPatterns, postStartupPayload } from './endpoint_client.js';
|
|
9
|
+
import { getFileCollectionPatterns, patchPayload, postStartupPayload, } from './endpoint_client.js';
|
|
10
10
|
import { runSensitivePathsAudit } from './log_sensitive_paths_audit.js';
|
|
11
11
|
const AUTH_KEY_RELATIVE_PATH = path.join('opt-ai-sec', 'management', 'auth_key.txt');
|
|
12
12
|
/** Claude installed plugins manifest (cache-based installs). */
|
|
@@ -122,6 +122,8 @@ const JSON_FILE_PATHS = [
|
|
|
122
122
|
const VSCDB_PATH = join(homedir(), 'Library', 'Application Support', 'Cursor', 'User', 'globalStorage', 'state.vscdb');
|
|
123
123
|
// Cursor extensions cache file path
|
|
124
124
|
const EXTENSIONS_CACHE_PATH = join(homedir(), 'Library', 'Application Support', 'Cursor', 'CachedProfilesData', '__default__profile__', 'extensions.user.cache');
|
|
125
|
+
// Cursor MCP tool cache: ~/.cursor/projects/<project>/mcps/<server>/tools/*.json
|
|
126
|
+
const CURSOR_PROJECTS_PATH = join(homedir(), '.cursor', 'projects');
|
|
125
127
|
// Claude configuration file paths
|
|
126
128
|
const CLAUDE_FILE_PATHS = [
|
|
127
129
|
// Project Root
|
|
@@ -221,13 +223,58 @@ function expandGlobPathPattern(pathPattern, fileType, home, projectRoot) {
|
|
|
221
223
|
}
|
|
222
224
|
return targets;
|
|
223
225
|
}
|
|
226
|
+
/**
|
|
227
|
+
* Expand Cursor MCP tool cache path to one target per tool file.
|
|
228
|
+
* Path: ~/.cursor/projects/<project>/mcps/<server>/tools/*.json
|
|
229
|
+
* Uses logicalFilePath (project_id/mcps/server_id/tools/name.json) for the payload.
|
|
230
|
+
*/
|
|
231
|
+
function expandMcpToolGlobPattern(home) {
|
|
232
|
+
const targets = [];
|
|
233
|
+
const projectsPath = join(home, '.cursor', 'projects');
|
|
234
|
+
if (!existsSync(projectsPath))
|
|
235
|
+
return targets;
|
|
236
|
+
try {
|
|
237
|
+
const projectDirs = readdirSync(projectsPath, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
238
|
+
for (const projectDir of projectDirs) {
|
|
239
|
+
const mcpsPath = join(projectsPath, projectDir.name, 'mcps');
|
|
240
|
+
if (!existsSync(mcpsPath))
|
|
241
|
+
continue;
|
|
242
|
+
const serverDirs = readdirSync(mcpsPath, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
243
|
+
for (const serverDir of serverDirs) {
|
|
244
|
+
const toolsPath = join(mcpsPath, serverDir.name, 'tools');
|
|
245
|
+
if (!existsSync(toolsPath))
|
|
246
|
+
continue;
|
|
247
|
+
const toolFiles = readdirSync(toolsPath).filter((n) => n.endsWith('.json'));
|
|
248
|
+
for (const toolFile of toolFiles) {
|
|
249
|
+
const fullPath = join(toolsPath, toolFile);
|
|
250
|
+
targets.push({
|
|
251
|
+
path: fullPath,
|
|
252
|
+
file_type: 'mcp_tool',
|
|
253
|
+
logicalFilePath: `${projectDir.name}/mcps/${serverDir.name}/tools/${toolFile}`,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
// ignore read errors
|
|
261
|
+
}
|
|
262
|
+
return targets;
|
|
263
|
+
}
|
|
224
264
|
/**
|
|
225
265
|
* Resolve a single (path_pattern, file_type) from the API to concrete collection targets.
|
|
226
266
|
*/
|
|
227
267
|
function resolvePatternToTargets(pathPattern, fileType, projectRoot, home) {
|
|
228
268
|
const norm = pathPattern.replace(/\\/g, '/');
|
|
229
269
|
const targets = [];
|
|
230
|
-
//
|
|
270
|
+
// mcp_tool: only the backend glob pattern drives collection; skip short "mcps/" (used for path classification only)
|
|
271
|
+
if (fileType === 'mcp_tool') {
|
|
272
|
+
if (norm.includes('*') && norm.includes('mcps') && norm.includes('tools')) {
|
|
273
|
+
return expandMcpToolGlobPattern(home);
|
|
274
|
+
}
|
|
275
|
+
return targets;
|
|
276
|
+
}
|
|
277
|
+
// Single-* glob pattern: expand ~/.claude/plugins/marketplaces/*/skills/ etc.
|
|
231
278
|
if (norm.includes('*')) {
|
|
232
279
|
return expandGlobPathPattern(pathPattern, fileType, home, projectRoot);
|
|
233
280
|
}
|
|
@@ -292,6 +339,20 @@ function collectConfigFilesFromPatterns(patterns, projectRoot) {
|
|
|
292
339
|
const home = homedir();
|
|
293
340
|
const seenPaths = new Set();
|
|
294
341
|
const targets = [];
|
|
342
|
+
const enrichByFileType = {};
|
|
343
|
+
for (const p of patterns) {
|
|
344
|
+
if (p.enrich && !enrichByFileType[p.file_type])
|
|
345
|
+
enrichByFileType[p.file_type] = p.enrich;
|
|
346
|
+
}
|
|
347
|
+
// Openclaw: we only collect package.json files (no version enrichment here; backend uses them in resource generation).
|
|
348
|
+
if (!enrichByFileType['openclaw_config']) {
|
|
349
|
+
enrichByFileType['openclaw_config'] = {
|
|
350
|
+
installs_path: 'plugins.installs',
|
|
351
|
+
installs_path_fallback: 'installs',
|
|
352
|
+
installs_shape: 'object',
|
|
353
|
+
id_field: 'id',
|
|
354
|
+
};
|
|
355
|
+
}
|
|
295
356
|
for (const { path_pattern, file_type } of patterns) {
|
|
296
357
|
for (const t of resolvePatternToTargets(path_pattern, file_type, projectRoot, home)) {
|
|
297
358
|
const key = `${t.path}\t${t.file_type}`;
|
|
@@ -395,14 +456,27 @@ function collectConfigFilesFromPatterns(patterns, projectRoot) {
|
|
|
395
456
|
const vscdbState = readVSCDBState();
|
|
396
457
|
if (vscdbState) {
|
|
397
458
|
if (vscdbState.composerState) {
|
|
459
|
+
const webToolsTopKeys = [
|
|
460
|
+
'lastBrowserConnectionMode',
|
|
461
|
+
'playwrightProtection',
|
|
462
|
+
'isWebSearchToolEnabled',
|
|
463
|
+
'isWebFetchToolEnabled',
|
|
464
|
+
'autoAcceptWebSearchTool',
|
|
465
|
+
];
|
|
466
|
+
const rawContent = {
|
|
467
|
+
composerState: vscdbState.composerState,
|
|
468
|
+
source: 'state.vscdb',
|
|
469
|
+
extracted_at: new Date().toISOString(),
|
|
470
|
+
};
|
|
471
|
+
for (const key of webToolsTopKeys) {
|
|
472
|
+
if (key in vscdbState && vscdbState[key] !== undefined) {
|
|
473
|
+
rawContent[key] = vscdbState[key];
|
|
474
|
+
}
|
|
475
|
+
}
|
|
398
476
|
configFiles.push({
|
|
399
477
|
file_type: 'vscode_settings',
|
|
400
478
|
file_path: `${VSCDB_PATH}#composerState`,
|
|
401
|
-
raw_content:
|
|
402
|
-
composerState: vscdbState.composerState,
|
|
403
|
-
source: 'state.vscdb',
|
|
404
|
-
extracted_at: new Date().toISOString(),
|
|
405
|
-
},
|
|
479
|
+
raw_content: rawContent,
|
|
406
480
|
});
|
|
407
481
|
}
|
|
408
482
|
const chat = vscdbState.chat;
|
|
@@ -482,6 +556,7 @@ function collectConfigFilesFromPatterns(patterns, projectRoot) {
|
|
|
482
556
|
if (!existsSync(t.path))
|
|
483
557
|
continue;
|
|
484
558
|
const isJson = t.path.endsWith('.json') ||
|
|
559
|
+
t.file_type === 'mcp_tool' ||
|
|
485
560
|
t.file_type === 'mcp_config' ||
|
|
486
561
|
t.file_type === 'cursor_hooks' ||
|
|
487
562
|
t.file_type === 'claude_settings';
|
|
@@ -498,15 +573,120 @@ function collectConfigFilesFromPatterns(patterns, projectRoot) {
|
|
|
498
573
|
else {
|
|
499
574
|
raw = typeof content === 'string' ? { content, source: 'file' } : content;
|
|
500
575
|
}
|
|
576
|
+
const recipe = enrichByFileType[t.file_type];
|
|
577
|
+
if (recipe && typeof raw === 'object' && raw !== null) {
|
|
578
|
+
enrichRawFromRecipe(raw, recipe);
|
|
579
|
+
}
|
|
501
580
|
configFiles.push({
|
|
502
581
|
file_type: t.file_type,
|
|
503
|
-
file_path: t.path,
|
|
582
|
+
file_path: t.logicalFilePath ?? t.path,
|
|
504
583
|
raw_content: raw,
|
|
505
584
|
});
|
|
585
|
+
// Openclaw: collect each install's package.json for backend to use in resource generation (version, etc.)
|
|
586
|
+
if (t.file_type === 'openclaw_config' && typeof raw === 'object' && raw !== null) {
|
|
587
|
+
pushOpenclawPackageJsonFiles(raw, configFiles);
|
|
588
|
+
}
|
|
506
589
|
}
|
|
507
590
|
}
|
|
508
591
|
return configFiles;
|
|
509
592
|
}
|
|
593
|
+
/**
|
|
594
|
+
* For openclaw_config raw content, push one openclaw_package_json config file per install path
|
|
595
|
+
* (installPath/package.json or sourcePath/package.json). Backend uses these during resource generation.
|
|
596
|
+
*/
|
|
597
|
+
function pushOpenclawPackageJsonFiles(raw, configFiles) {
|
|
598
|
+
let installs = getByPath(raw, 'plugins.installs');
|
|
599
|
+
if (!installs)
|
|
600
|
+
installs = getByPath(raw, 'installs');
|
|
601
|
+
if (!installs)
|
|
602
|
+
return;
|
|
603
|
+
const entries = Array.isArray(installs)
|
|
604
|
+
? installs.filter((e) => typeof e === 'object' && e !== null)
|
|
605
|
+
: typeof installs === 'object' && installs !== null
|
|
606
|
+
? Object.values(installs).filter((e) => typeof e === 'object' && e !== null)
|
|
607
|
+
: [];
|
|
608
|
+
for (const entry of entries) {
|
|
609
|
+
const basePath = (entry.installPath ?? entry.sourcePath ?? '').trim();
|
|
610
|
+
if (!basePath)
|
|
611
|
+
continue;
|
|
612
|
+
const pkgPath = join(basePath, 'package.json');
|
|
613
|
+
const pkg = readJSONFile(pkgPath);
|
|
614
|
+
if (pkg) {
|
|
615
|
+
configFiles.push({
|
|
616
|
+
file_type: 'openclaw_package_json',
|
|
617
|
+
file_path: pkgPath,
|
|
618
|
+
raw_content: pkg,
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Get a nested value from raw by dot-notation path (e.g. "plugins.installs").
|
|
625
|
+
*/
|
|
626
|
+
function getByPath(obj, path) {
|
|
627
|
+
const parts = path.split('.');
|
|
628
|
+
let cur = obj;
|
|
629
|
+
for (const p of parts) {
|
|
630
|
+
if (cur == null || typeof cur !== 'object')
|
|
631
|
+
return undefined;
|
|
632
|
+
cur = cur[p];
|
|
633
|
+
}
|
|
634
|
+
return cur;
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Enrich raw_content using backend-provided recipe. Flow: 1) install manifest (path from recipe),
|
|
638
|
+
* 2) read install location from each entry, 3) find file (path_template), 4) read version_key.
|
|
639
|
+
* Mutates raw in place. Only runs when recipe has version_from_file and installs_path.
|
|
640
|
+
*/
|
|
641
|
+
function enrichRawFromRecipe(raw, recipe) {
|
|
642
|
+
const installsPath = recipe.installs_path ?? recipe.installs_path_fallback;
|
|
643
|
+
if (!installsPath)
|
|
644
|
+
return;
|
|
645
|
+
let installs = getByPath(raw, installsPath);
|
|
646
|
+
if (!installs && recipe.installs_path_fallback && recipe.installs_path_fallback !== installsPath) {
|
|
647
|
+
installs = getByPath(raw, recipe.installs_path_fallback);
|
|
648
|
+
}
|
|
649
|
+
if (!installs)
|
|
650
|
+
return;
|
|
651
|
+
const vf = recipe.version_from_file;
|
|
652
|
+
if (!vf?.version_key)
|
|
653
|
+
return;
|
|
654
|
+
const resolveVersion = (e) => {
|
|
655
|
+
// 1) Use version from install manifest if present
|
|
656
|
+
const v = e.version;
|
|
657
|
+
if (v !== undefined && v !== null && (typeof v !== 'string' || v.trim() !== ''))
|
|
658
|
+
return;
|
|
659
|
+
// 2) Else get from installPath/package.json (or sourcePath/package.json)
|
|
660
|
+
const installPath = e.installPath?.trim();
|
|
661
|
+
const sourcePath = e.sourcePath?.trim();
|
|
662
|
+
const basePath = installPath || sourcePath;
|
|
663
|
+
if (!basePath)
|
|
664
|
+
return;
|
|
665
|
+
const pkgPath = join(basePath, 'package.json');
|
|
666
|
+
const pkg = readJSONFile(pkgPath);
|
|
667
|
+
const versionKey = vf.version_key;
|
|
668
|
+
const versionVal = versionKey && pkg ? pkg[versionKey] : undefined;
|
|
669
|
+
if (typeof versionVal === 'string')
|
|
670
|
+
e.version = versionVal.trim();
|
|
671
|
+
};
|
|
672
|
+
if (recipe.installs_shape === 'array' && Array.isArray(installs)) {
|
|
673
|
+
for (const entry of installs) {
|
|
674
|
+
if (typeof entry === 'object' && entry !== null)
|
|
675
|
+
resolveVersion(entry);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
else if (typeof installs === 'object' && installs !== null) {
|
|
679
|
+
for (const key of Object.keys(installs)) {
|
|
680
|
+
const entry = installs[key];
|
|
681
|
+
if (typeof entry === 'object' && entry !== null) {
|
|
682
|
+
const e = entry;
|
|
683
|
+
if (recipe.installs_shape === 'object' && !('id' in e && e.id != null))
|
|
684
|
+
e.id = key;
|
|
685
|
+
resolveVersion(e);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
510
690
|
/**
|
|
511
691
|
* Read and parse an MCP config file
|
|
512
692
|
*/
|
|
@@ -544,6 +724,47 @@ function readJSONFile(filePath) {
|
|
|
544
724
|
return null;
|
|
545
725
|
}
|
|
546
726
|
}
|
|
727
|
+
/**
|
|
728
|
+
* Collect MCP tool cache files from ~/.cursor/projects/<project>/mcps/<server>/tools/*.json.
|
|
729
|
+
* Iterates all project and server directories; each tool JSON is sent as a separate file so
|
|
730
|
+
* inventory can list tools per MCP server. Path sent is logical: <project>/mcps/<server>/tools/<name>.json
|
|
731
|
+
*/
|
|
732
|
+
function collectMcpToolFiles() {
|
|
733
|
+
const result = [];
|
|
734
|
+
try {
|
|
735
|
+
if (!existsSync(CURSOR_PROJECTS_PATH)) {
|
|
736
|
+
return result;
|
|
737
|
+
}
|
|
738
|
+
const projectDirs = readdirSync(CURSOR_PROJECTS_PATH, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
739
|
+
for (const projectDir of projectDirs) {
|
|
740
|
+
const mcpsPath = join(CURSOR_PROJECTS_PATH, projectDir.name, 'mcps');
|
|
741
|
+
if (!existsSync(mcpsPath))
|
|
742
|
+
continue;
|
|
743
|
+
const serverDirs = readdirSync(mcpsPath, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
744
|
+
for (const serverDir of serverDirs) {
|
|
745
|
+
const toolsPath = join(mcpsPath, serverDir.name, 'tools');
|
|
746
|
+
if (!existsSync(toolsPath))
|
|
747
|
+
continue;
|
|
748
|
+
const toolFiles = readdirSync(toolsPath).filter((n) => n.endsWith('.json'));
|
|
749
|
+
for (const toolFile of toolFiles) {
|
|
750
|
+
const fullPath = join(toolsPath, toolFile);
|
|
751
|
+
const content = readJSONFile(fullPath);
|
|
752
|
+
if (content) {
|
|
753
|
+
result.push({
|
|
754
|
+
file_type: 'mcp_tool',
|
|
755
|
+
file_path: `${projectDir.name}/mcps/${serverDir.name}/tools/${toolFile}`,
|
|
756
|
+
raw_content: content,
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
catch (error) {
|
|
764
|
+
console.warn('Error collecting MCP tool files:', error instanceof Error ? error.message : String(error));
|
|
765
|
+
}
|
|
766
|
+
return result;
|
|
767
|
+
}
|
|
547
768
|
/**
|
|
548
769
|
* Read a markdown/text file and return as content string
|
|
549
770
|
*/
|
|
@@ -1168,10 +1389,26 @@ function readVSCDBState(dbPath) {
|
|
|
1168
1389
|
if (composerResult && composerResult !== '') {
|
|
1169
1390
|
try {
|
|
1170
1391
|
const parsed = JSON.parse(composerResult);
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1392
|
+
if (parsed && typeof parsed === 'object') {
|
|
1393
|
+
// Extract composerState from the nested structure
|
|
1394
|
+
const composerState = (parsed.composerState ?? parsed);
|
|
1395
|
+
if (composerState && typeof composerState === 'object') {
|
|
1396
|
+
stateData.composerState = composerState;
|
|
1397
|
+
}
|
|
1398
|
+
// Cursor may store lastBrowserConnectionMode (and other Web Tools keys) at the top level
|
|
1399
|
+
// of the reactive storage blob. Include them so the backend can merge into composerState.
|
|
1400
|
+
const topLevelKeys = [
|
|
1401
|
+
'lastBrowserConnectionMode',
|
|
1402
|
+
'playwrightProtection',
|
|
1403
|
+
'isWebSearchToolEnabled',
|
|
1404
|
+
'isWebFetchToolEnabled',
|
|
1405
|
+
'autoAcceptWebSearchTool',
|
|
1406
|
+
];
|
|
1407
|
+
for (const key of topLevelKeys) {
|
|
1408
|
+
if (key in parsed && parsed[key] !== undefined) {
|
|
1409
|
+
stateData[key] = parsed[key];
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1175
1412
|
}
|
|
1176
1413
|
}
|
|
1177
1414
|
catch (parseError) {
|
|
@@ -1405,6 +1642,17 @@ function collectConfigFiles() {
|
|
|
1405
1642
|
});
|
|
1406
1643
|
}
|
|
1407
1644
|
}
|
|
1645
|
+
// MCP tool files: same location as backend pattern ~/.cursor/projects/*/mcps/*/tools/*.json (see expandMcpToolGlobPattern)
|
|
1646
|
+
for (const t of expandMcpToolGlobPattern(homedir())) {
|
|
1647
|
+
const content = readJSONFile(t.path);
|
|
1648
|
+
if (content != null) {
|
|
1649
|
+
configFiles.push({
|
|
1650
|
+
file_type: 'mcp_tool',
|
|
1651
|
+
file_path: t.logicalFilePath ?? t.path,
|
|
1652
|
+
raw_content: content,
|
|
1653
|
+
});
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1408
1656
|
// Read JSON files - collect all levels (enterprise, project, user) so we can display them all
|
|
1409
1657
|
for (const { path, file_type } of JSON_FILE_PATHS) {
|
|
1410
1658
|
try {
|
|
@@ -1427,16 +1675,30 @@ function collectConfigFiles() {
|
|
|
1427
1675
|
const vscdbState = readVSCDBState();
|
|
1428
1676
|
if (vscdbState) {
|
|
1429
1677
|
console.log(`Found Cursor state data at: ${VSCDB_PATH}`);
|
|
1430
|
-
// Store composerState as vscode_settings (general VS Code/Cursor state)
|
|
1678
|
+
// Store composerState as vscode_settings (general VS Code/Cursor state).
|
|
1679
|
+
// Include top-level keys (e.g. lastBrowserConnectionMode) so backend can merge into composerState for Web Tools policy.
|
|
1431
1680
|
if (vscdbState.composerState) {
|
|
1681
|
+
const topLevelKeys = [
|
|
1682
|
+
'lastBrowserConnectionMode',
|
|
1683
|
+
'playwrightProtection',
|
|
1684
|
+
'isWebSearchToolEnabled',
|
|
1685
|
+
'isWebFetchToolEnabled',
|
|
1686
|
+
'autoAcceptWebSearchTool',
|
|
1687
|
+
];
|
|
1688
|
+
const rawContent = {
|
|
1689
|
+
composerState: vscdbState.composerState,
|
|
1690
|
+
source: 'state.vscdb',
|
|
1691
|
+
extracted_at: new Date().toISOString(),
|
|
1692
|
+
};
|
|
1693
|
+
for (const key of topLevelKeys) {
|
|
1694
|
+
if (key in vscdbState && vscdbState[key] !== undefined) {
|
|
1695
|
+
rawContent[key] = vscdbState[key];
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1432
1698
|
configFiles.push({
|
|
1433
1699
|
file_type: 'vscode_settings',
|
|
1434
1700
|
file_path: `${VSCDB_PATH}#composerState`, // Use fragment to distinguish
|
|
1435
|
-
raw_content:
|
|
1436
|
-
composerState: vscdbState.composerState,
|
|
1437
|
-
source: 'state.vscdb',
|
|
1438
|
-
extracted_at: new Date().toISOString(),
|
|
1439
|
-
},
|
|
1701
|
+
raw_content: rawContent,
|
|
1440
1702
|
});
|
|
1441
1703
|
}
|
|
1442
1704
|
// Store chat.tools as vscode_settings (for chat.tools.autoApprove policy)
|
|
@@ -2097,17 +2359,12 @@ async function sendHookRequestUpdateManifest(hardwareUuid, authKey, hookRequestI
|
|
|
2097
2359
|
const signature = createSignature(payload, authKey.key);
|
|
2098
2360
|
const body = { ...payload, signature, key_id: authKey.key_id || '' };
|
|
2099
2361
|
try {
|
|
2100
|
-
const
|
|
2101
|
-
|
|
2102
|
-
headers: { 'Content-Type': 'application/json' },
|
|
2103
|
-
body: JSON.stringify(body),
|
|
2104
|
-
});
|
|
2105
|
-
const data = (await response.json());
|
|
2106
|
-
if (response.ok && data.status === 'accepted') {
|
|
2362
|
+
const data = (await patchPayload(apiUrl, body));
|
|
2363
|
+
if (data.status === 'accepted') {
|
|
2107
2364
|
hookRunLog(`hook-request manifest updated (${manifestNormalized.length} paths)`);
|
|
2108
2365
|
return true;
|
|
2109
2366
|
}
|
|
2110
|
-
hookRunLog(`hook-request update failed: ${data.error
|
|
2367
|
+
hookRunLog(`hook-request update failed: ${data.error ?? data.status ?? 'unknown'}`);
|
|
2111
2368
|
return false;
|
|
2112
2369
|
}
|
|
2113
2370
|
catch (error) {
|
|
@@ -2306,6 +2563,10 @@ function determineFileTypeFromPath(filePath) {
|
|
|
2306
2563
|
normalizedPath.includes('.mcp.json')) {
|
|
2307
2564
|
return 'mcp_config';
|
|
2308
2565
|
}
|
|
2566
|
+
// Cursor MCP tool cache: .../mcps/<server>/tools/*.json
|
|
2567
|
+
if (normalizedPath.includes('mcps/') && normalizedPath.includes('/tools/')) {
|
|
2568
|
+
return 'mcp_tool';
|
|
2569
|
+
}
|
|
2309
2570
|
// Claude settings (most specific first)
|
|
2310
2571
|
if (normalizedPath.includes('/Library/Application Support/ClaudeCode/managed-settings.json')) {
|
|
2311
2572
|
return 'claude_settings';
|
|
@@ -2387,6 +2648,13 @@ async function logSingleFile(filePath) {
|
|
|
2387
2648
|
// Read the SQLite database using the provided path
|
|
2388
2649
|
const vscdbState = readVSCDBState(actualPath);
|
|
2389
2650
|
if (vscdbState) {
|
|
2651
|
+
const webToolsTopKeys = [
|
|
2652
|
+
'lastBrowserConnectionMode',
|
|
2653
|
+
'playwrightProtection',
|
|
2654
|
+
'isWebSearchToolEnabled',
|
|
2655
|
+
'isWebFetchToolEnabled',
|
|
2656
|
+
'autoAcceptWebSearchTool',
|
|
2657
|
+
];
|
|
2390
2658
|
// Check if there's a fragment (e.g., "#composerState" or "#chat.tools")
|
|
2391
2659
|
const fragment = filePath.includes('#') ? filePath.split('#')[1] : null;
|
|
2392
2660
|
if (fragment === 'composerState' && vscdbState.composerState) {
|
|
@@ -2395,6 +2663,11 @@ async function logSingleFile(filePath) {
|
|
|
2395
2663
|
source: 'state.vscdb',
|
|
2396
2664
|
extracted_at: new Date().toISOString(),
|
|
2397
2665
|
};
|
|
2666
|
+
for (const key of webToolsTopKeys) {
|
|
2667
|
+
if (key in vscdbState && vscdbState[key] !== undefined) {
|
|
2668
|
+
rawContent[key] = vscdbState[key];
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2398
2671
|
fileType = 'vscode_settings';
|
|
2399
2672
|
}
|
|
2400
2673
|
else if (fragment === 'chat.tools' && vscdbState.chat) {
|
|
@@ -2441,6 +2714,11 @@ async function logSingleFile(filePath) {
|
|
|
2441
2714
|
source: 'state.vscdb',
|
|
2442
2715
|
extracted_at: new Date().toISOString(),
|
|
2443
2716
|
};
|
|
2717
|
+
for (const key of webToolsTopKeys) {
|
|
2718
|
+
if (key in vscdbState && vscdbState[key] !== undefined) {
|
|
2719
|
+
rawContent[key] = vscdbState[key];
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2444
2722
|
fileType = 'vscode_settings';
|
|
2445
2723
|
}
|
|
2446
2724
|
else {
|
|
@@ -2597,4 +2875,4 @@ if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.includes
|
|
|
2597
2875
|
process.exit(1);
|
|
2598
2876
|
});
|
|
2599
2877
|
}
|
|
2600
|
-
export { main, collectConfigFiles, collectConfigFilesFromPatterns, collectConfigFilesFromInstalledPlugins, resolvePatternToTargets, ensureAuthentication, sendConfigFile, sendConfigFilesBatch, createSignature, canonicalizePayload, readVSCDBState, logSingleFile, };
|
|
2878
|
+
export { main, collectConfigFiles, collectConfigFilesFromPatterns, collectConfigFilesFromInstalledPlugins, collectMcpToolFiles, resolvePatternToTargets, ensureAuthentication, sendConfigFile, sendConfigFilesBatch, createSignature, canonicalizePayload, readVSCDBState, logSingleFile, };
|