log-llm-config 1.0.7 → 1.0.9

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.
@@ -36,6 +36,11 @@ const JSON_FILE_PATHS = [
36
36
  path: join(homedir(), 'Library', 'Application Support', 'Cursor', 'hooks.json'),
37
37
  file_type: 'cursor_hooks',
38
38
  },
39
+ // Project-level Cursor Cloud Agent environment config
40
+ {
41
+ path: join(PROJECT_ROOT, '.cursor', 'environment.json'),
42
+ file_type: 'cursor_cloud_agent_config',
43
+ },
39
44
  // Project-level Claude settings (preferred over user)
40
45
  {
41
46
  path: join(PROJECT_ROOT, '.claude', 'settings.json'),
@@ -69,8 +74,22 @@ const CLAUDE_FILE_PATHS = [
69
74
  ];
70
75
  // Claude rules directory
71
76
  const CLAUDE_RULES_DIR = join(homedir(), '.claude', 'rules');
77
+ // Claude subagents directories
78
+ const CLAUDE_AGENTS_PROJECT_DIR = join(PROJECT_ROOT, '.claude', 'agents');
79
+ const CLAUDE_AGENTS_USER_DIR = join(homedir(), '.claude', 'agents');
72
80
  // Cursor rules directory (project-level)
73
81
  const CURSOR_RULES_DIR = join(PROJECT_ROOT, '.cursor', 'rules');
82
+ // Cursor commands directories
83
+ const CURSOR_COMMANDS_PROJECT_DIR = join(PROJECT_ROOT, '.cursor', 'commands');
84
+ const CURSOR_COMMANDS_USER_DIR = join(homedir(), '.cursor', 'commands');
85
+ // Cursor subagents directory (project-level)
86
+ const CURSOR_AGENTS_DIR = join(PROJECT_ROOT, '.cursor', 'agents');
87
+ // Plugin directories to scan
88
+ // Claude plugins can be in various locations (project, user home, etc.)
89
+ const PLUGIN_SEARCH_DIRS = [
90
+ PROJECT_ROOT, // Project-level plugins
91
+ join(homedir(), '.claude', 'plugins'), // User-level plugins (if this becomes a standard location)
92
+ ];
74
93
  /**
75
94
  * Read and parse an MCP config file
76
95
  */
@@ -177,6 +196,90 @@ function getClaudeRuleFiles() {
177
196
  }
178
197
  return files;
179
198
  }
199
+ /**
200
+ * Get all command files from Cursor commands directories
201
+ * Collects from both project-level (.cursor/commands/) and user-level (~/.cursor/commands/)
202
+ */
203
+ function getCursorCommandFiles() {
204
+ const files = [];
205
+ // Collect from project-level commands directory
206
+ try {
207
+ if (existsSync(CURSOR_COMMANDS_PROJECT_DIR)) {
208
+ const entries = readdirSync(CURSOR_COMMANDS_PROJECT_DIR, { withFileTypes: true });
209
+ for (const entry of entries) {
210
+ if (entry.isFile() && entry.name.endsWith('.md')) {
211
+ files.push({
212
+ path: join(CURSOR_COMMANDS_PROJECT_DIR, entry.name),
213
+ file_type: 'cursor_command',
214
+ });
215
+ }
216
+ }
217
+ }
218
+ }
219
+ catch (error) {
220
+ console.warn(`Error reading Cursor project commands directory ${CURSOR_COMMANDS_PROJECT_DIR}:`, error instanceof Error ? error.message : String(error));
221
+ }
222
+ // Collect from user-level commands directory
223
+ try {
224
+ if (existsSync(CURSOR_COMMANDS_USER_DIR)) {
225
+ const entries = readdirSync(CURSOR_COMMANDS_USER_DIR, { withFileTypes: true });
226
+ for (const entry of entries) {
227
+ if (entry.isFile() && entry.name.endsWith('.md')) {
228
+ files.push({
229
+ path: join(CURSOR_COMMANDS_USER_DIR, entry.name),
230
+ file_type: 'cursor_command',
231
+ });
232
+ }
233
+ }
234
+ }
235
+ }
236
+ catch (error) {
237
+ console.warn(`Error reading Cursor user commands directory ${CURSOR_COMMANDS_USER_DIR}:`, error instanceof Error ? error.message : String(error));
238
+ }
239
+ return files;
240
+ }
241
+ /**
242
+ * Get all subagent files from Claude agents directories
243
+ * Collects from both project-level (.claude/agents/) and user-level (~/.claude/agents/)
244
+ */
245
+ function getClaudeSubagentFiles() {
246
+ const files = [];
247
+ // Collect from project-level agents directory
248
+ try {
249
+ if (existsSync(CLAUDE_AGENTS_PROJECT_DIR)) {
250
+ const entries = readdirSync(CLAUDE_AGENTS_PROJECT_DIR, { withFileTypes: true });
251
+ for (const entry of entries) {
252
+ if (entry.isFile() && entry.name.endsWith('.md')) {
253
+ files.push({
254
+ path: join(CLAUDE_AGENTS_PROJECT_DIR, entry.name),
255
+ file_type: 'claude_subagent',
256
+ });
257
+ }
258
+ }
259
+ }
260
+ }
261
+ catch (error) {
262
+ console.warn(`Error reading Claude project agents directory ${CLAUDE_AGENTS_PROJECT_DIR}:`, error instanceof Error ? error.message : String(error));
263
+ }
264
+ // Collect from user-level agents directory
265
+ try {
266
+ if (existsSync(CLAUDE_AGENTS_USER_DIR)) {
267
+ const entries = readdirSync(CLAUDE_AGENTS_USER_DIR, { withFileTypes: true });
268
+ for (const entry of entries) {
269
+ if (entry.isFile() && entry.name.endsWith('.md')) {
270
+ files.push({
271
+ path: join(CLAUDE_AGENTS_USER_DIR, entry.name),
272
+ file_type: 'claude_subagent',
273
+ });
274
+ }
275
+ }
276
+ }
277
+ }
278
+ catch (error) {
279
+ console.warn(`Error reading Claude user agents directory ${CLAUDE_AGENTS_USER_DIR}:`, error instanceof Error ? error.message : String(error));
280
+ }
281
+ return files;
282
+ }
180
283
  /**
181
284
  * Get all rule files from Cursor rules directory
182
285
  * Supports new format (.cursor/rules/rule-name/RULE.md) and legacy format (.cursor/rules/*.mdc or *.md)
@@ -263,6 +366,167 @@ function getCursorRulesFile(projectRoot) {
263
366
  }
264
367
  return files;
265
368
  }
369
+ /**
370
+ * Find all Claude plugin directories by scanning for .claude-plugin/plugin.json files
371
+ * Searches in project root and common plugin locations
372
+ */
373
+ function findClaudePluginDirectories() {
374
+ const plugins = [];
375
+ // Search in project root and subdirectories (up to 3 levels deep)
376
+ const searchDirs = [PROJECT_ROOT];
377
+ // Also check common plugin locations
378
+ const commonPluginPaths = [
379
+ join(PROJECT_ROOT, 'plugins'),
380
+ join(PROJECT_ROOT, '.plugins'),
381
+ join(homedir(), '.claude', 'plugins'),
382
+ ];
383
+ for (const searchPath of [...searchDirs, ...commonPluginPaths]) {
384
+ if (!existsSync(searchPath)) {
385
+ continue;
386
+ }
387
+ try {
388
+ // Recursively search for .claude-plugin directories
389
+ const findPluginDirs = (dir, depth = 0) => {
390
+ if (depth > 3)
391
+ return; // Limit recursion depth
392
+ // Skip common directories that shouldn't contain plugins
393
+ const skipDirs = ['node_modules', '.git', '.venv', 'venv', 'dist', 'build', '.next', '.cache'];
394
+ const dirName = path.basename(dir);
395
+ if (skipDirs.includes(dirName)) {
396
+ return;
397
+ }
398
+ try {
399
+ const entries = readdirSync(dir, { withFileTypes: true });
400
+ for (const entry of entries) {
401
+ if (entry.isDirectory()) {
402
+ // Check if this is a .claude-plugin directory
403
+ if (entry.name === '.claude-plugin') {
404
+ const manifestPath = join(dir, '.claude-plugin', 'plugin.json');
405
+ if (existsSync(manifestPath)) {
406
+ plugins.push({
407
+ pluginDir: dir,
408
+ manifestPath: manifestPath,
409
+ });
410
+ }
411
+ // Don't recurse into .claude-plugin directory
412
+ continue;
413
+ }
414
+ // Skip hidden directories (except .claude-plugin which we already handled)
415
+ // and common build/cache directories
416
+ if (entry.name.startsWith('.') || skipDirs.includes(entry.name)) {
417
+ continue;
418
+ }
419
+ // Recursively search subdirectories
420
+ findPluginDirs(join(dir, entry.name), depth + 1);
421
+ }
422
+ }
423
+ }
424
+ catch (error) {
425
+ // Skip directories we can't read
426
+ }
427
+ };
428
+ findPluginDirs(searchPath);
429
+ }
430
+ catch (error) {
431
+ // Skip if we can't read the directory
432
+ }
433
+ }
434
+ return plugins;
435
+ }
436
+ /**
437
+ * Get Claude plugin manifest files (.claude-plugin/plugin.json)
438
+ */
439
+ function getClaudePluginManifests() {
440
+ const files = [];
441
+ const plugins = findClaudePluginDirectories();
442
+ for (const { pluginDir, manifestPath } of plugins) {
443
+ files.push({
444
+ path: manifestPath,
445
+ file_type: 'claude_plugin_manifest',
446
+ pluginDir: pluginDir,
447
+ });
448
+ }
449
+ return files;
450
+ }
451
+ /**
452
+ * Get command files from a plugin directory (commands/*.md)
453
+ */
454
+ function getPluginCommandFiles(pluginDir) {
455
+ const files = [];
456
+ // pluginDir is the directory containing .claude-plugin, so commands are at pluginDir/.claude-plugin/commands
457
+ const commandsDir = join(pluginDir, '.claude-plugin', 'commands');
458
+ try {
459
+ if (!existsSync(commandsDir)) {
460
+ return files;
461
+ }
462
+ const entries = readdirSync(commandsDir, { withFileTypes: true });
463
+ for (const entry of entries) {
464
+ if (entry.isFile() && entry.name.endsWith('.md')) {
465
+ files.push({
466
+ path: join(commandsDir, entry.name),
467
+ file_type: 'claude_plugin_command',
468
+ });
469
+ }
470
+ }
471
+ }
472
+ catch (error) {
473
+ console.warn(`Error reading plugin commands directory ${commandsDir}:`, error instanceof Error ? error.message : String(error));
474
+ }
475
+ return files;
476
+ }
477
+ /**
478
+ * Get hook files from a plugin directory (hooks/hooks.json)
479
+ */
480
+ function getPluginHookFiles(pluginDir) {
481
+ const files = [];
482
+ // pluginDir is the directory containing .claude-plugin, so hooks are at pluginDir/.claude-plugin/hooks/hooks.json
483
+ const hooksPath = join(pluginDir, '.claude-plugin', 'hooks', 'hooks.json');
484
+ if (existsSync(hooksPath)) {
485
+ files.push({
486
+ path: hooksPath,
487
+ file_type: 'claude_plugin_hooks',
488
+ });
489
+ }
490
+ return files;
491
+ }
492
+ /**
493
+ * Get MCP config files from a plugin directory (.mcp.json)
494
+ */
495
+ function getPluginAgentFiles(pluginDir) {
496
+ const files = [];
497
+ // pluginDir is the directory containing .claude-plugin, so agents are at pluginDir/.claude-plugin/agents/*.md
498
+ const agentsDir = join(pluginDir, '.claude-plugin', 'agents');
499
+ try {
500
+ if (!existsSync(agentsDir)) {
501
+ return files;
502
+ }
503
+ const entries = readdirSync(agentsDir, { withFileTypes: true });
504
+ for (const entry of entries) {
505
+ if (entry.isFile() && entry.name.endsWith('.md')) {
506
+ files.push({
507
+ path: join(agentsDir, entry.name),
508
+ file_type: 'claude_subagent',
509
+ });
510
+ }
511
+ }
512
+ }
513
+ catch (error) {
514
+ console.warn(`Error reading plugin agents directory ${agentsDir}:`, error instanceof Error ? error.message : String(error));
515
+ }
516
+ return files;
517
+ }
518
+ function getPluginMcpFiles(pluginDir) {
519
+ const files = [];
520
+ // pluginDir is the directory containing .claude-plugin, so MCP config is at pluginDir/.claude-plugin/.mcp.json
521
+ const mcpPath = join(pluginDir, '.claude-plugin', '.mcp.json');
522
+ if (existsSync(mcpPath)) {
523
+ files.push({
524
+ path: mcpPath,
525
+ file_type: 'claude_plugin_mcp',
526
+ });
527
+ }
528
+ return files;
529
+ }
266
530
  /**
267
531
  * Get subdirectory Claude files for monorepo support
268
532
  * Checks immediate subdirectories (1 level deep) for CLAUDE.md files
@@ -293,7 +557,8 @@ function getSubdirectoryClaudeFiles(projectRoot) {
293
557
  return files;
294
558
  }
295
559
  /**
296
- * Read composerState from Cursor's state.vscdb SQLite database
560
+ * Read state data from Cursor's state.vscdb SQLite database
561
+ * Extracts composerState, extensionsAssistant, and extensions data
297
562
  */
298
563
  function readVSCDBState() {
299
564
  try {
@@ -308,34 +573,128 @@ function readVSCDBState() {
308
573
  console.warn('sqlite3 command not found; skipping state.vscdb reading');
309
574
  return null;
310
575
  }
576
+ const stateData = {};
311
577
  // Query the SQLite database for composerState
312
578
  // Escape single quotes in the key for SQL safety
313
- const key = "src.vs.platform.reactivestorage.browser.reactiveStorageServiceImpl.persistentStorage.applicationUser";
314
- const escapedKey = key.replace(/'/g, "''");
315
- const query = `SELECT value FROM ItemTable WHERE key='${escapedKey}'`;
316
- const result = execSync(`sqlite3 "${VSCDB_PATH}" "${query}"`, {
317
- encoding: 'utf8',
318
- stdio: ['ignore', 'pipe', 'pipe'],
319
- }).trim();
320
- if (!result || result === '') {
321
- return null;
322
- }
323
- // Parse the JSON value
324
- let parsed;
579
+ const composerStateKey = "src.vs.platform.reactivestorage.browser.reactiveStorageServiceImpl.persistentStorage.applicationUser";
580
+ const escapedComposerKey = composerStateKey.replace(/'/g, "''");
581
+ const composerQuery = `SELECT value FROM ItemTable WHERE key='${escapedComposerKey}'`;
325
582
  try {
326
- parsed = JSON.parse(result);
583
+ const composerResult = execSync(`sqlite3 "${VSCDB_PATH}" "${composerQuery}"`, {
584
+ encoding: 'utf8',
585
+ stdio: ['ignore', 'pipe', 'pipe'],
586
+ }).trim();
587
+ if (composerResult && composerResult !== '') {
588
+ try {
589
+ const parsed = JSON.parse(composerResult);
590
+ // Extract composerState from the nested structure
591
+ const composerState = parsed?.composerState || parsed;
592
+ if (composerState && typeof composerState === 'object') {
593
+ stateData.composerState = composerState;
594
+ }
595
+ }
596
+ catch (parseError) {
597
+ console.warn(`Failed to parse composerState from state.vscdb:`, parseError instanceof Error ? parseError.message : String(parseError));
598
+ }
599
+ }
327
600
  }
328
- catch (parseError) {
329
- console.error(`Failed to parse JSON from state.vscdb:`, parseError instanceof Error ? parseError.message : String(parseError));
330
- return null;
601
+ catch (error) {
602
+ console.warn(`Error reading composerState from state.vscdb:`, error instanceof Error ? error.message : String(error));
331
603
  }
332
- // Extract composerState from the nested structure
333
- // The structure might be: { composerState: {...} } or just the composerState object
334
- const composerState = parsed?.composerState || parsed;
335
- if (!composerState || typeof composerState !== 'object') {
336
- return null;
604
+ // Query for extensionsAssistant data
605
+ // The actual key in state.vscdb is "extensionsAssistant/recommendations" (with forward slash)
606
+ const extensionKeys = [
607
+ "extensionsAssistant/recommendations", // Actual key format in state.vscdb
608
+ "extensionsAssistant.recommendations",
609
+ "workbench.extensionsAssistant.recommendations",
610
+ ];
611
+ for (const key of extensionKeys) {
612
+ try {
613
+ const escapedKey = key.replace(/'/g, "''");
614
+ const query = `SELECT value FROM ItemTable WHERE key='${escapedKey}'`;
615
+ const result = execSync(`sqlite3 "${VSCDB_PATH}" "${query}"`, {
616
+ encoding: 'utf8',
617
+ stdio: ['ignore', 'pipe', 'pipe'],
618
+ }).trim();
619
+ if (result && result !== '') {
620
+ try {
621
+ const parsed = JSON.parse(result);
622
+ // The value is an object with extension IDs as keys and timestamps as values
623
+ // Convert to array of extension IDs for the recommendations path
624
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
625
+ const extensionIds = Object.keys(parsed);
626
+ stateData.extensionsAssistant = { recommendations: extensionIds };
627
+ }
628
+ else if (Array.isArray(parsed)) {
629
+ stateData.extensionsAssistant = { recommendations: parsed };
630
+ }
631
+ break; // Found it, stop searching
632
+ }
633
+ catch (parseError) {
634
+ // Continue to next key
635
+ }
636
+ }
637
+ }
638
+ catch (error) {
639
+ // Continue to next key
640
+ }
337
641
  }
338
- return composerState;
642
+ // Query for extensions.trustedPublishers data
643
+ // The actual key in state.vscdb is "extensions.trustedPublishers"
644
+ const trustedPublisherKeys = [
645
+ "extensions.trustedPublishers", // Actual key format in state.vscdb
646
+ "workbench.extensions.trustedPublishers",
647
+ ];
648
+ for (const key of trustedPublisherKeys) {
649
+ try {
650
+ const escapedKey = key.replace(/'/g, "''");
651
+ const query = `SELECT value FROM ItemTable WHERE key='${escapedKey}'`;
652
+ const result = execSync(`sqlite3 "${VSCDB_PATH}" "${query}"`, {
653
+ encoding: 'utf8',
654
+ stdio: ['ignore', 'pipe', 'pipe'],
655
+ }).trim();
656
+ if (result && result !== '') {
657
+ try {
658
+ const parsed = JSON.parse(result);
659
+ // The value is an object with publisher IDs as keys and publisher info as values
660
+ // Extract publisher IDs/names for the trustedPublishers array
661
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
662
+ // Extract publisher IDs from the object keys
663
+ const publisherIds = Object.keys(parsed);
664
+ stateData.extensions = { trustedPublishers: publisherIds };
665
+ }
666
+ else if (Array.isArray(parsed)) {
667
+ stateData.extensions = { trustedPublishers: parsed };
668
+ }
669
+ break; // Found it, stop searching
670
+ }
671
+ catch (parseError) {
672
+ // Continue to next key
673
+ }
674
+ }
675
+ }
676
+ catch (error) {
677
+ // Continue to next key
678
+ }
679
+ }
680
+ // If we found composerState, also check if extensionsAssistant/extensions are nested within it
681
+ if (stateData.composerState && typeof stateData.composerState === 'object') {
682
+ const composerState = stateData.composerState;
683
+ // Check if extensionsAssistant is in composerState
684
+ if (composerState.extensionsAssistant && !stateData.extensionsAssistant) {
685
+ stateData.extensionsAssistant = composerState.extensionsAssistant;
686
+ }
687
+ // Check if extensions is in composerState
688
+ if (composerState.extensions && !stateData.extensions) {
689
+ stateData.extensions = composerState.extensions;
690
+ }
691
+ }
692
+ // Return the combined state data (composerState, extensionsAssistant, extensions)
693
+ // If we have any data, return it; otherwise return null
694
+ if (Object.keys(stateData).length > 0) {
695
+ return stateData;
696
+ }
697
+ return null;
339
698
  }
340
699
  catch (error) {
341
700
  console.error(`Error reading state.vscdb:`, error instanceof Error ? error.message : String(error));
@@ -381,15 +740,51 @@ function collectConfigFiles() {
381
740
  const vscdbState = readVSCDBState();
382
741
  if (vscdbState) {
383
742
  console.log(`Found Cursor state data at: ${VSCDB_PATH}`);
384
- configFiles.push({
385
- file_type: 'vscode_settings', // Using vscode_settings type for state.vscdb (composerState)
386
- file_path: VSCDB_PATH,
387
- raw_content: {
388
- composerState: vscdbState,
389
- source: 'state.vscdb',
390
- extracted_at: new Date().toISOString(),
391
- },
392
- });
743
+ // Store composerState as vscode_settings (general VS Code/Cursor state)
744
+ if (vscdbState.composerState) {
745
+ configFiles.push({
746
+ file_type: 'vscode_settings',
747
+ file_path: `${VSCDB_PATH}#composerState`, // Use fragment to distinguish
748
+ raw_content: {
749
+ composerState: vscdbState.composerState,
750
+ source: 'state.vscdb',
751
+ extracted_at: new Date().toISOString(),
752
+ },
753
+ });
754
+ }
755
+ // Store extensionsAssistant.recommendations as cursor_extensions
756
+ // (same type as .vscode/extensions.json recommendations)
757
+ // Structure must match evaluation plan path: extensionsAssistant.recommendations
758
+ const extensionsAssistant = vscdbState.extensionsAssistant;
759
+ if (extensionsAssistant?.recommendations && Array.isArray(extensionsAssistant.recommendations)) {
760
+ configFiles.push({
761
+ file_type: 'cursor_extensions',
762
+ file_path: `${VSCDB_PATH}#extensionsAssistant.recommendations`, // Use fragment to distinguish
763
+ raw_content: {
764
+ extensionsAssistant: {
765
+ recommendations: extensionsAssistant.recommendations,
766
+ },
767
+ source: 'state.vscdb',
768
+ extracted_at: new Date().toISOString(),
769
+ },
770
+ });
771
+ }
772
+ // Store extensions.trustedPublishers as cursor_extensions
773
+ // (extension-related data, should be checked by extension policies)
774
+ const extensions = vscdbState.extensions;
775
+ if (extensions?.trustedPublishers && Array.isArray(extensions.trustedPublishers)) {
776
+ configFiles.push({
777
+ file_type: 'cursor_extensions',
778
+ file_path: `${VSCDB_PATH}#extensions.trustedPublishers`, // Use fragment to distinguish
779
+ raw_content: {
780
+ extensions: {
781
+ trustedPublishers: extensions.trustedPublishers,
782
+ },
783
+ source: 'state.vscdb',
784
+ extracted_at: new Date().toISOString(),
785
+ },
786
+ });
787
+ }
393
788
  }
394
789
  // Read Claude configuration files (project root, local overrides, home directory)
395
790
  for (const { path, file_type } of CLAUDE_FILE_PATHS) {
@@ -474,6 +869,48 @@ function collectConfigFiles() {
474
869
  console.warn(`Error reading ${path}: ${error instanceof Error ? error.message : String(error)}`);
475
870
  }
476
871
  }
872
+ // Read Claude subagent files from .claude/agents/*.md and ~/.claude/agents/*.md
873
+ const claudeSubagentFiles = getClaudeSubagentFiles();
874
+ for (const { path, file_type } of claudeSubagentFiles) {
875
+ try {
876
+ const markdownContent = readMarkdownFile(path);
877
+ if (markdownContent !== null) { // Log even if empty
878
+ console.log(`Found ${file_type} at: ${path}`);
879
+ configFiles.push({
880
+ file_type,
881
+ file_path: path,
882
+ raw_content: {
883
+ content: markdownContent,
884
+ source: 'claude_subagent_file',
885
+ },
886
+ });
887
+ }
888
+ }
889
+ catch (error) {
890
+ console.warn(`Error reading ${path}: ${error instanceof Error ? error.message : String(error)}`);
891
+ }
892
+ }
893
+ // Read Cursor command files from .cursor/commands/*.md and ~/.cursor/commands/*.md
894
+ const cursorCommandFiles = getCursorCommandFiles();
895
+ for (const { path, file_type } of cursorCommandFiles) {
896
+ try {
897
+ const markdownContent = readMarkdownFile(path);
898
+ if (markdownContent !== null) { // Log even if empty
899
+ console.log(`Found ${file_type} at: ${path}`);
900
+ configFiles.push({
901
+ file_type,
902
+ file_path: path,
903
+ raw_content: {
904
+ content: markdownContent,
905
+ source: 'cursor_command_file',
906
+ },
907
+ });
908
+ }
909
+ }
910
+ catch (error) {
911
+ console.warn(`Error reading ${path}: ${error instanceof Error ? error.message : String(error)}`);
912
+ }
913
+ }
477
914
  // Read Cursor rule files from .cursor/rules/ (new format: folders with RULE.md, legacy: .mdc/.md files)
478
915
  const cursorRuleFiles = getCursorRuleFiles();
479
916
  for (const { path, file_type } of cursorRuleFiles) {
@@ -537,6 +974,126 @@ function collectConfigFiles() {
537
974
  console.warn(`Error reading ${path}: ${error instanceof Error ? error.message : String(error)}`);
538
975
  }
539
976
  }
977
+ // Read Cursor extension recommendations (.vscode/extensions.json)
978
+ const extensionsJsonPath = join(PROJECT_ROOT, '.vscode', 'extensions.json');
979
+ if (existsSync(extensionsJsonPath)) {
980
+ try {
981
+ const extensionsContent = readJSONFile(extensionsJsonPath);
982
+ if (extensionsContent) {
983
+ console.log(`Found Cursor extensions.json at: ${extensionsJsonPath}`);
984
+ configFiles.push({
985
+ file_type: 'cursor_extensions',
986
+ file_path: extensionsJsonPath,
987
+ raw_content: extensionsContent,
988
+ });
989
+ }
990
+ }
991
+ catch (error) {
992
+ console.warn(`Error reading ${extensionsJsonPath}: ${error instanceof Error ? error.message : String(error)}`);
993
+ }
994
+ }
995
+ // Read Claude plugin manifests and related files
996
+ const pluginManifests = getClaudePluginManifests();
997
+ for (const { path, file_type, pluginDir } of pluginManifests) {
998
+ try {
999
+ const manifestContent = readJSONFile(path);
1000
+ if (manifestContent) {
1001
+ configFiles.push({
1002
+ file_type,
1003
+ file_path: path,
1004
+ raw_content: manifestContent,
1005
+ });
1006
+ // Collect plugin command files (commands/*.md)
1007
+ const commandFiles = getPluginCommandFiles(pluginDir);
1008
+ for (const { path: cmdPath, file_type: cmdFileType } of commandFiles) {
1009
+ try {
1010
+ const cmdContent = readMarkdownFile(cmdPath);
1011
+ if (cmdContent !== null) {
1012
+ configFiles.push({
1013
+ file_type: cmdFileType,
1014
+ file_path: cmdPath,
1015
+ raw_content: {
1016
+ content: cmdContent,
1017
+ source: 'claude_rule_file', // Plugin command files are .md rule files
1018
+ pluginDir: pluginDir,
1019
+ pluginCommandFile: true, // Additional flag to identify as plugin command
1020
+ },
1021
+ });
1022
+ }
1023
+ }
1024
+ catch (error) {
1025
+ console.warn(`Error reading plugin command ${cmdPath}:`, error instanceof Error ? error.message : String(error));
1026
+ }
1027
+ }
1028
+ // Collect plugin hook files (hooks/hooks.json)
1029
+ const hookFiles = getPluginHookFiles(pluginDir);
1030
+ for (const { path: hookPath, file_type: hookFileType } of hookFiles) {
1031
+ try {
1032
+ const hookContent = readJSONFile(hookPath);
1033
+ if (hookContent) {
1034
+ configFiles.push({
1035
+ file_type: hookFileType,
1036
+ file_path: hookPath,
1037
+ raw_content: {
1038
+ ...hookContent,
1039
+ source: 'claude_plugin_hooks_file',
1040
+ pluginDir: pluginDir,
1041
+ },
1042
+ });
1043
+ }
1044
+ }
1045
+ catch (error) {
1046
+ console.warn(`Error reading plugin hooks ${hookPath}:`, error instanceof Error ? error.message : String(error));
1047
+ }
1048
+ }
1049
+ // Collect plugin agent files (agents/*.md)
1050
+ const agentFiles = getPluginAgentFiles(pluginDir);
1051
+ for (const { path: agentPath, file_type: agentFileType } of agentFiles) {
1052
+ try {
1053
+ const agentContent = readMarkdownFile(agentPath);
1054
+ if (agentContent !== null) {
1055
+ configFiles.push({
1056
+ file_type: agentFileType,
1057
+ file_path: agentPath,
1058
+ raw_content: {
1059
+ content: agentContent,
1060
+ source: 'claude_plugin_agent_file',
1061
+ pluginDir: pluginDir,
1062
+ },
1063
+ });
1064
+ }
1065
+ }
1066
+ catch (error) {
1067
+ console.warn(`Error reading plugin agent ${agentPath}:`, error instanceof Error ? error.message : String(error));
1068
+ }
1069
+ }
1070
+ // Collect plugin MCP config files (.mcp.json)
1071
+ const mcpFiles = getPluginMcpFiles(pluginDir);
1072
+ for (const { path: mcpPath, file_type: mcpFileType } of mcpFiles) {
1073
+ try {
1074
+ const mcpContent = readJSONFile(mcpPath);
1075
+ if (mcpContent) {
1076
+ configFiles.push({
1077
+ file_type: mcpFileType,
1078
+ file_path: mcpPath,
1079
+ raw_content: {
1080
+ ...mcpContent,
1081
+ source: 'claude_plugin_mcp_file',
1082
+ pluginDir: pluginDir,
1083
+ },
1084
+ });
1085
+ }
1086
+ }
1087
+ catch (error) {
1088
+ console.warn(`Error reading plugin MCP config ${mcpPath}:`, error instanceof Error ? error.message : String(error));
1089
+ }
1090
+ }
1091
+ }
1092
+ }
1093
+ catch (error) {
1094
+ console.warn(`Error reading plugin manifest ${path}:`, error instanceof Error ? error.message : String(error));
1095
+ }
1096
+ }
540
1097
  return configFiles;
541
1098
  }
542
1099
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "log-llm-config",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "CLI helpers for logging hardware UUIDs and posting startup payloads to Optimus Security.",
5
5
  "type": "module",
6
6
  "bin": {