log-llm-config 1.0.20 → 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.
@@ -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, patchPayload, 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). */
@@ -339,6 +339,20 @@ function collectConfigFilesFromPatterns(patterns, projectRoot) {
339
339
  const home = homedir();
340
340
  const seenPaths = new Set();
341
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
+ }
342
356
  for (const { path_pattern, file_type } of patterns) {
343
357
  for (const t of resolvePatternToTargets(path_pattern, file_type, projectRoot, home)) {
344
358
  const key = `${t.path}\t${t.file_type}`;
@@ -442,14 +456,27 @@ function collectConfigFilesFromPatterns(patterns, projectRoot) {
442
456
  const vscdbState = readVSCDBState();
443
457
  if (vscdbState) {
444
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
+ }
445
476
  configFiles.push({
446
477
  file_type: 'vscode_settings',
447
478
  file_path: `${VSCDB_PATH}#composerState`,
448
- raw_content: {
449
- composerState: vscdbState.composerState,
450
- source: 'state.vscdb',
451
- extracted_at: new Date().toISOString(),
452
- },
479
+ raw_content: rawContent,
453
480
  });
454
481
  }
455
482
  const chat = vscdbState.chat;
@@ -546,15 +573,120 @@ function collectConfigFilesFromPatterns(patterns, projectRoot) {
546
573
  else {
547
574
  raw = typeof content === 'string' ? { content, source: 'file' } : content;
548
575
  }
576
+ const recipe = enrichByFileType[t.file_type];
577
+ if (recipe && typeof raw === 'object' && raw !== null) {
578
+ enrichRawFromRecipe(raw, recipe);
579
+ }
549
580
  configFiles.push({
550
581
  file_type: t.file_type,
551
582
  file_path: t.logicalFilePath ?? t.path,
552
583
  raw_content: raw,
553
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
+ }
554
589
  }
555
590
  }
556
591
  return configFiles;
557
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
+ }
558
690
  /**
559
691
  * Read and parse an MCP config file
560
692
  */
@@ -1257,10 +1389,26 @@ function readVSCDBState(dbPath) {
1257
1389
  if (composerResult && composerResult !== '') {
1258
1390
  try {
1259
1391
  const parsed = JSON.parse(composerResult);
1260
- // Extract composerState from the nested structure
1261
- const composerState = parsed?.composerState || parsed;
1262
- if (composerState && typeof composerState === 'object') {
1263
- 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
+ }
1264
1412
  }
1265
1413
  }
1266
1414
  catch (parseError) {
@@ -1527,16 +1675,30 @@ function collectConfigFiles() {
1527
1675
  const vscdbState = readVSCDBState();
1528
1676
  if (vscdbState) {
1529
1677
  console.log(`Found Cursor state data at: ${VSCDB_PATH}`);
1530
- // 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.
1531
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
+ }
1532
1698
  configFiles.push({
1533
1699
  file_type: 'vscode_settings',
1534
1700
  file_path: `${VSCDB_PATH}#composerState`, // Use fragment to distinguish
1535
- raw_content: {
1536
- composerState: vscdbState.composerState,
1537
- source: 'state.vscdb',
1538
- extracted_at: new Date().toISOString(),
1539
- },
1701
+ raw_content: rawContent,
1540
1702
  });
1541
1703
  }
1542
1704
  // Store chat.tools as vscode_settings (for chat.tools.autoApprove policy)
@@ -2486,6 +2648,13 @@ async function logSingleFile(filePath) {
2486
2648
  // Read the SQLite database using the provided path
2487
2649
  const vscdbState = readVSCDBState(actualPath);
2488
2650
  if (vscdbState) {
2651
+ const webToolsTopKeys = [
2652
+ 'lastBrowserConnectionMode',
2653
+ 'playwrightProtection',
2654
+ 'isWebSearchToolEnabled',
2655
+ 'isWebFetchToolEnabled',
2656
+ 'autoAcceptWebSearchTool',
2657
+ ];
2489
2658
  // Check if there's a fragment (e.g., "#composerState" or "#chat.tools")
2490
2659
  const fragment = filePath.includes('#') ? filePath.split('#')[1] : null;
2491
2660
  if (fragment === 'composerState' && vscdbState.composerState) {
@@ -2494,6 +2663,11 @@ async function logSingleFile(filePath) {
2494
2663
  source: 'state.vscdb',
2495
2664
  extracted_at: new Date().toISOString(),
2496
2665
  };
2666
+ for (const key of webToolsTopKeys) {
2667
+ if (key in vscdbState && vscdbState[key] !== undefined) {
2668
+ rawContent[key] = vscdbState[key];
2669
+ }
2670
+ }
2497
2671
  fileType = 'vscode_settings';
2498
2672
  }
2499
2673
  else if (fragment === 'chat.tools' && vscdbState.chat) {
@@ -2540,6 +2714,11 @@ async function logSingleFile(filePath) {
2540
2714
  source: 'state.vscdb',
2541
2715
  extracted_at: new Date().toISOString(),
2542
2716
  };
2717
+ for (const key of webToolsTopKeys) {
2718
+ if (key in vscdbState && vscdbState[key] !== undefined) {
2719
+ rawContent[key] = vscdbState[key];
2720
+ }
2721
+ }
2543
2722
  fileType = 'vscode_settings';
2544
2723
  }
2545
2724
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "log-llm-config",
3
- "version": "1.0.20",
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": {