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.
@@ -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 {
@@ -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
- // Glob pattern: expand ~/.claude/plugins/marketplaces/*/skills/ etc.
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
- // Extract composerState from the nested structure
1172
- const composerState = parsed?.composerState || parsed;
1173
- if (composerState && typeof composerState === 'object') {
1174
- stateData.composerState = composerState;
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 response = await fetch(apiUrl, {
2101
- method: 'PATCH',
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 || response.status}`);
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, };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "log-llm-config",
3
- "version": "1.0.19",
3
+ "version": "1.0.22",
4
4
  "description": "CLI helpers for logging hardware UUIDs and posting startup payloads to Optimus Security.",
5
5
  "type": "module",
6
6
  "bin": {