log-llm-config-staging 1.3.44

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.
Files changed (51) hide show
  1. package/README.md +46 -0
  2. package/dist/apply_deferred_vscdb.js +8 -0
  3. package/dist/bootstrap_constants.js +5 -0
  4. package/dist/cli/bash_script_generator.js +95 -0
  5. package/dist/cli.js +103 -0
  6. package/dist/cli_invocation_match.js +28 -0
  7. package/dist/compliance_check_runner.js +17 -0
  8. package/dist/compliance_prompt_gate.js +197 -0
  9. package/dist/endpoint_client/http_transport.js +88 -0
  10. package/dist/endpoint_client/index.js +3 -0
  11. package/dist/endpoint_client/registry_api.js +41 -0
  12. package/dist/endpoint_client/startup_api.js +43 -0
  13. package/dist/endpoint_client/types.js +4 -0
  14. package/dist/execute_trusted_restarts.js +54 -0
  15. package/dist/log_config_files/auth/auth_flow.js +22 -0
  16. package/dist/log_config_files/auth/auth_key_store.js +14 -0
  17. package/dist/log_config_files/collection/config_collector.js +160 -0
  18. package/dist/log_config_files/collection/directory_collector.js +96 -0
  19. package/dist/log_config_files/collection/enrichment_helpers.js +53 -0
  20. package/dist/log_config_files/collection/file_type_rules.js +47 -0
  21. package/dist/log_config_files/collection/mcp_tool_collector.js +37 -0
  22. package/dist/log_config_files/collection/openclaw_helpers.js +55 -0
  23. package/dist/log_config_files/collection/plugin_collector.js +89 -0
  24. package/dist/log_config_files/collection/plugin_version_helpers.js +37 -0
  25. package/dist/log_config_files/index.js +19 -0
  26. package/dist/log_config_files/paths/path_constants_helpers.js +71 -0
  27. package/dist/log_config_files/paths/pattern_resolver.js +227 -0
  28. package/dist/log_config_files/readers/file_readers.js +69 -0
  29. package/dist/log_config_files/readers/vscdb_config_builder.js +146 -0
  30. package/dist/log_config_files/readers/vscdb_reader.js +247 -0
  31. package/dist/log_config_files/runtime/compliance_check.js +518 -0
  32. package/dist/log_config_files/runtime/hardware_uuid.js +36 -0
  33. package/dist/log_config_files/runtime/hook_logger.js +197 -0
  34. package/dist/log_config_files/runtime/main_runner.js +192 -0
  35. package/dist/log_config_files/runtime/management_storage.js +82 -0
  36. package/dist/log_config_files/runtime/remediation_config_path.js +90 -0
  37. package/dist/log_config_files/runtime/remediation_sync.js +1290 -0
  38. package/dist/log_config_files/runtime/sqlite_binary.js +92 -0
  39. package/dist/log_config_files/runtime/trusted_restarts.js +52 -0
  40. package/dist/log_config_files/sender/batch_sender.js +220 -0
  41. package/dist/log_config_files/sender/endpoint_config.js +24 -0
  42. package/dist/log_config_files/sender/signing.js +1 -0
  43. package/dist/log_sensitive_paths_audit.js +97 -0
  44. package/dist/log_uuid/auth_key_store.js +71 -0
  45. package/dist/log_uuid/hardware_uuid.js +35 -0
  46. package/dist/log_uuid/index.js +11 -0
  47. package/dist/log_uuid/log_uuid_helper.js +30 -0
  48. package/dist/log_uuid/startup_sender.js +74 -0
  49. package/dist/log_uuid/user_profile.js +178 -0
  50. package/dist/types/config_file_types.js +1 -0
  51. package/package.json +62 -0
@@ -0,0 +1,89 @@
1
+ import { existsSync, readdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ import { readJSONFile, readMarkdownFile } from '../readers/file_readers.js';
5
+ import { getInstalledPluginsPath, getPluginHooksPath, getPluginMcpPaths, getPluginSkillFilename, getPluginSkillsDir } from '../paths/path_constants_helpers.js';
6
+ import { versionFromPluginCachePath, parseInstalledPluginKey } from './plugin_version_helpers.js';
7
+ function collectSkillFiles(skillsDir, results, constants) {
8
+ const skillFilename = getPluginSkillFilename(constants);
9
+ try {
10
+ for (const d of readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory())) {
11
+ const skillPath = join(skillsDir, d.name, skillFilename);
12
+ if (!existsSync(skillPath))
13
+ continue;
14
+ const content = readMarkdownFile(skillPath);
15
+ if (content !== null) {
16
+ const ver = versionFromPluginCachePath(skillPath, constants);
17
+ results.push({ file_type: 'claude_skill', file_path: skillPath, raw_content: { content, source: 'file', ...(ver ? { version: ver } : {}) } });
18
+ }
19
+ }
20
+ }
21
+ catch (err) {
22
+ console.warn(`Error reading skills dir ${skillsDir}:`, err instanceof Error ? err.message : String(err));
23
+ }
24
+ }
25
+ function collectMcpFile(installPath, results, constants) {
26
+ const mcpPaths = getPluginMcpPaths(constants);
27
+ for (const rel of mcpPaths) {
28
+ const mcpPath = join(installPath, rel);
29
+ if (!existsSync(mcpPath))
30
+ continue;
31
+ const content = readJSONFile(mcpPath);
32
+ if (content !== null) {
33
+ const ver = versionFromPluginCachePath(mcpPath, constants);
34
+ results.push({ file_type: 'claude_plugin_mcp', file_path: mcpPath, raw_content: typeof content === 'object' ? { ...content, ...(ver ? { version: ver } : {}) } : content });
35
+ break;
36
+ }
37
+ }
38
+ }
39
+ function collectPluginInstallEntries(pluginKey, entries, results, constants) {
40
+ const { name: pluginName, publisher } = parseInstalledPluginKey(pluginKey);
41
+ const skillsDirName = getPluginSkillsDir(constants);
42
+ const hooksRel = getPluginHooksPath(constants);
43
+ for (const entry of entries) {
44
+ const installPath = entry?.installPath;
45
+ if (!installPath || typeof installPath !== 'string' || !existsSync(installPath))
46
+ continue;
47
+ const version = (entry?.version != null && entry?.version !== '') ? String(entry.version).trim() : '';
48
+ results.push({ file_type: 'claude_plugin_manifest', file_path: installPath, raw_content: { name: pluginName || pluginKey, version, publisher: publisher || '' } });
49
+ const skillsPath = join(installPath, skillsDirName);
50
+ if (existsSync(skillsPath))
51
+ collectSkillFiles(skillsPath, results, constants);
52
+ const hooksPath = join(installPath, hooksRel);
53
+ if (existsSync(hooksPath)) {
54
+ const content = readJSONFile(hooksPath);
55
+ if (content !== null) {
56
+ const ver = versionFromPluginCachePath(hooksPath, constants);
57
+ results.push({ file_type: 'claude_hooks', file_path: hooksPath, raw_content: typeof content === 'object' ? { ...content, ...(ver ? { version: ver } : {}) } : content });
58
+ }
59
+ }
60
+ collectMcpFile(installPath, results, constants);
61
+ }
62
+ }
63
+ function collectConfigFilesFromInstalledPlugins(constants) {
64
+ const home = homedir();
65
+ const installedPluginsPath = getInstalledPluginsPath(home, constants);
66
+ const results = [];
67
+ if (!existsSync(installedPluginsPath))
68
+ return results;
69
+ let manifest;
70
+ try {
71
+ const raw = readJSONFile(installedPluginsPath);
72
+ if (!raw)
73
+ return results;
74
+ manifest = raw;
75
+ }
76
+ catch {
77
+ return results;
78
+ }
79
+ const plugins = manifest?.plugins;
80
+ if (!plugins || typeof plugins !== 'object')
81
+ return results;
82
+ results.push({ file_type: 'claude_plugin_manifest', file_path: installedPluginsPath, raw_content: manifest });
83
+ for (const [pluginKey, entries] of Object.entries(plugins)) {
84
+ if (Array.isArray(entries))
85
+ collectPluginInstallEntries(pluginKey, entries, results, constants);
86
+ }
87
+ return results;
88
+ }
89
+ export { collectConfigFilesFromInstalledPlugins };
@@ -0,0 +1,37 @@
1
+ /** Needles used to identify the plugin segment within a cache path. Requires backend client_path_constants. */
2
+ function getPluginPathNeedles(constants) {
3
+ const needles = [];
4
+ if (constants.plugin_skills_dir)
5
+ needles.push(`/${constants.plugin_skills_dir}/`);
6
+ if (constants.plugin_hooks_path)
7
+ needles.push(`/${constants.plugin_hooks_path}`);
8
+ for (const p of constants.plugin_mcp_paths ?? [])
9
+ needles.push(`/${p}`);
10
+ if (!needles.length)
11
+ throw new Error('client_path_constants missing plugin path keys (plugin_skills_dir, plugin_hooks_path, plugin_mcp_paths)');
12
+ return needles;
13
+ }
14
+ function versionFromPluginCachePath(filePath, constants) {
15
+ const norm = filePath.replace(/\\/g, '/');
16
+ if (!norm.includes('plugins/cache/'))
17
+ return '';
18
+ const needles = getPluginPathNeedles(constants);
19
+ let segmentEnd = -1;
20
+ for (const needle of needles) {
21
+ const idx = norm.indexOf(needle);
22
+ if (idx !== -1 && (segmentEnd === -1 || idx < segmentEnd))
23
+ segmentEnd = idx;
24
+ }
25
+ if (segmentEnd === -1)
26
+ return '';
27
+ const before = norm.slice(0, segmentEnd);
28
+ const lastSlash = before.lastIndexOf('/');
29
+ return lastSlash === -1 ? '' : before.slice(lastSlash + 1).trim();
30
+ }
31
+ function parseInstalledPluginKey(pluginKey) {
32
+ const at = pluginKey.indexOf('@');
33
+ if (at === -1)
34
+ return { name: pluginKey.trim(), publisher: '' };
35
+ return { name: pluginKey.slice(0, at).trim(), publisher: pluginKey.slice(at + 1).trim() };
36
+ }
37
+ export { versionFromPluginCachePath, parseInstalledPluginKey };
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ // Barrel — all public exports. Sub-modules live in this folder.
3
+ export { BATCH_CHUNK_SIZE } from './sender/batch_sender.js';
4
+ export { sendConfigFile, sendConfigFilesBatch, sendIngestSessionStart, sendIngestSessionFinish } from './sender/batch_sender.js';
5
+ export { createSignature, canonicalizePayload } from './sender/signing.js';
6
+ export { readVSCDBState } from './readers/vscdb_reader.js';
7
+ export { readJSONFile, readMarkdownFile } from './readers/file_readers.js';
8
+ export { resolvePatternToTargets } from './paths/pattern_resolver.js';
9
+ export { ensureAuthentication } from './auth/auth_flow.js';
10
+ export { collectConfigFilesFromPatterns, collectConfigFilesFromInstalledPlugins, collectMcpToolFiles, determineFileTypeFromPath, } from './collection/config_collector.js';
11
+ export { main, logSingleFile } from './runtime/main_runner.js';
12
+ // Entry point guard (when invoked directly via npx or node)
13
+ if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.includes('log_config_files')) {
14
+ const { main } = await import('./runtime/main_runner.js');
15
+ main().catch((error) => {
16
+ console.error(error instanceof Error ? error.message : String(error));
17
+ process.exit(1);
18
+ });
19
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Build paths from backend client_path_constants + home.
3
+ * All functions require constants from the API response — no hardcoded fallbacks.
4
+ * Throws if constants or a required key is missing.
5
+ */
6
+ import { join } from 'node:path';
7
+ function requireKey(constants, key, label) {
8
+ const val = constants[key];
9
+ if (val === undefined || val === null)
10
+ throw new Error(`client_path_constants missing required key "${String(key)}" (${label})`);
11
+ return val;
12
+ }
13
+ export function getVscdbPath(home, constants) {
14
+ const segments = requireKey(constants, 'vscdb_path_under_home', 'vscdb path segments');
15
+ return join(home, ...segments);
16
+ }
17
+ export function getExtensionsCachePath(home, constants) {
18
+ const segments = requireKey(constants, 'extensions_cache_path_under_home', 'extensions cache path segments');
19
+ return join(home, ...segments);
20
+ }
21
+ export function getCursorProjectsPath(home, constants) {
22
+ const rel = requireKey(constants, 'cursor_projects_path_under_home', 'cursor projects path');
23
+ return join(home, rel.replace(/^~\//, ''));
24
+ }
25
+ export function getInstalledPluginsPath(home, constants) {
26
+ const rel = requireKey(constants, 'installed_plugins_path_under_home', 'installed plugins path');
27
+ return join(home, rel.replace(/^~\//, ''));
28
+ }
29
+ export function getOptAiSecManagementPath(home, constants) {
30
+ const rel = requireKey(constants, 'opt_ai_sec_management_under_home', 'opt-ai-sec management path');
31
+ return join(home, rel.replace(/^~\//, ''));
32
+ }
33
+ export function getAuthKeyPathFromConstants(home, constants) {
34
+ const base = getOptAiSecManagementPath(home, constants);
35
+ const filename = requireKey(constants, 'auth_key_filename', 'auth key filename');
36
+ return join(base, filename);
37
+ }
38
+ export function getHookLogPathFromConstants(home, constants) {
39
+ const base = getOptAiSecManagementPath(home, constants);
40
+ const filename = requireKey(constants, 'hook_log_filename', 'hook log filename');
41
+ return join(base, filename);
42
+ }
43
+ export function getSensitivePathsAuditPathFromConstants(home, constants) {
44
+ const base = getOptAiSecManagementPath(home, constants);
45
+ const filename = requireKey(constants, 'sensitive_paths_audit_filename', 'sensitive paths audit filename');
46
+ return join(base, filename);
47
+ }
48
+ export function getExtensionsCacheInstalledSuffix(constants) {
49
+ return requireKey(constants, 'extensions_cache_installed_suffix', 'extensions cache installed suffix');
50
+ }
51
+ export function getPluginSkillFilename(constants) {
52
+ return requireKey(constants, 'plugin_skill_filename', 'plugin skill filename');
53
+ }
54
+ export function getPluginSkillsDir(constants) {
55
+ return requireKey(constants, 'plugin_skills_dir', 'plugin skills directory');
56
+ }
57
+ export function getPluginHooksPath(constants) {
58
+ return requireKey(constants, 'plugin_hooks_path', 'plugin hooks path');
59
+ }
60
+ export function getPluginMcpPaths(constants) {
61
+ return requireKey(constants, 'plugin_mcp_paths', 'plugin mcp paths');
62
+ }
63
+ /** Build path from a path_constant_key (from PATH_RESOLUTION_SPECS). Keeps pattern_resolver data-driven. */
64
+ export function getPathByConstantKey(home, constants, pathConstantKey) {
65
+ const val = constants[pathConstantKey];
66
+ if (Array.isArray(val))
67
+ return join(home, ...val);
68
+ if (typeof val === 'string')
69
+ return join(home, val.replace(/^~\//, ''));
70
+ throw new Error(`client_path_constants missing required key "${pathConstantKey}"`);
71
+ }
@@ -0,0 +1,227 @@
1
+ import { existsSync, readdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { getPathByConstantKey, getExtensionsCacheInstalledSuffix } from './path_constants_helpers.js';
4
+ const RECURSIVE_GLOB_MAX_DEPTH = 20;
5
+ /** Only non-empty strings (empty would match all dirs via startsWith('')). */
6
+ function normalizePathSkipPrefixes(prefixes) {
7
+ if (!Array.isArray(prefixes))
8
+ return [];
9
+ return prefixes.filter((p) => typeof p === 'string' && p.length > 0);
10
+ }
11
+ function expandRecursiveGlobPathPattern(pathPattern, fileType, home, contentFormat, dirGlob, homeRecurseSkipDirs = []) {
12
+ const norm = pathPattern.replace(/\\/g, '/');
13
+ const doubleStarIndex = norm.indexOf('**');
14
+ if (doubleStarIndex === -1)
15
+ return [];
16
+ const before = norm.slice(0, doubleStarIndex).replace(/\/+$/, '');
17
+ const after = norm.slice(doubleStarIndex + 2).replace(/^\/+/, '');
18
+ if (!after)
19
+ return [];
20
+ let basePath;
21
+ if (before === '~' || before.startsWith('~/')) {
22
+ basePath = join(home, before === '~' ? '' : before.slice(2));
23
+ }
24
+ else {
25
+ return [];
26
+ }
27
+ const targets = [];
28
+ const parts = after.split('/');
29
+ const dirName = parts[0];
30
+ const fileName = parts.length > 1 ? parts[parts.length - 1] : null;
31
+ const skipSet = homeRecurseSkipDirs.length ? new Set(homeRecurseSkipDirs) : new Set();
32
+ function walk(dir, depth, isHomeRoot) {
33
+ if (depth > RECURSIVE_GLOB_MAX_DEPTH || !existsSync(dir))
34
+ return;
35
+ try {
36
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
37
+ const full = join(dir, entry.name);
38
+ if (!entry.isDirectory())
39
+ continue;
40
+ if (isHomeRoot && skipSet.has(entry.name))
41
+ continue;
42
+ if (entry.name === dirName) {
43
+ const filePath = parts.length === 1 ? full : join(full, ...parts.slice(1));
44
+ if (fileName && existsSync(filePath))
45
+ targets.push({ path: filePath, file_type: fileType, content_format: contentFormat });
46
+ }
47
+ walk(full, depth + 1, false);
48
+ }
49
+ }
50
+ catch { /* ignore read errors */ }
51
+ }
52
+ walk(basePath, 0, true);
53
+ return targets;
54
+ }
55
+ function expandGlobPathPattern(pathPattern, fileType, home, projectRoot, contentFormat, dirGlob, absolutePathPrefixes = []) {
56
+ const norm = pathPattern.replace(/\\/g, '/');
57
+ const targets = [];
58
+ const asteriskIndex = norm.indexOf('*');
59
+ if (asteriskIndex === -1)
60
+ return targets;
61
+ const isDir = norm.endsWith('/');
62
+ const [before, after] = norm.split('*');
63
+ const afterNorm = after.replace(/^\/+/, '');
64
+ // If `before` doesn't end with '/', the * is mid-segment (e.g. "extensions/saoudrizwan.claude-dev*/" →
65
+ // parent="extensions/", namePrefix="saoudrizwan.claude-dev"). Split on last '/' to get the real base dir.
66
+ let parentPart = before.replace(/\/+$/, '');
67
+ let namePrefix = '';
68
+ if (!before.endsWith('/')) {
69
+ const lastSlash = parentPart.lastIndexOf('/');
70
+ if (lastSlash !== -1) {
71
+ namePrefix = parentPart.slice(lastSlash + 1);
72
+ parentPart = parentPart.slice(0, lastSlash);
73
+ }
74
+ }
75
+ let basePath;
76
+ if (parentPart.startsWith('~/')) {
77
+ basePath = join(home, parentPart.slice(2));
78
+ }
79
+ else if (parentPart.startsWith('/') && absolutePathPrefixes.some((prefix) => parentPart.startsWith(prefix))) {
80
+ basePath = parentPart;
81
+ }
82
+ else {
83
+ basePath = join(projectRoot, parentPart.startsWith('/') ? parentPart.slice(1) : parentPart);
84
+ }
85
+ if (!existsSync(basePath))
86
+ return targets;
87
+ try {
88
+ for (const entry of readdirSync(basePath, { withFileTypes: true })) {
89
+ if (!entry.isDirectory())
90
+ continue;
91
+ if (namePrefix && !entry.name.startsWith(namePrefix))
92
+ continue;
93
+ const resolvedPath = join(basePath, entry.name, afterNorm);
94
+ if (!existsSync(resolvedPath))
95
+ continue;
96
+ targets.push({ path: resolvedPath, file_type: fileType, isDirectory: isDir, dir_glob: dirGlob, content_format: contentFormat });
97
+ }
98
+ }
99
+ catch { /* ignore read errors */ }
100
+ return targets;
101
+ }
102
+ /**
103
+ * Expand MCP tool glob from backend spec. No hardcoded .cursor/mcps/tools; structure comes from API.
104
+ */
105
+ function expandGlobFromSpec(spec, home, pathSkipPrefixes = []) {
106
+ const targets = [];
107
+ const basePath = join(home, spec.base_path_under_home.replace(/^~\//, ''));
108
+ if (!existsSync(basePath))
109
+ return targets;
110
+ const skipPrefixes = normalizePathSkipPrefixes(pathSkipPrefixes);
111
+ function matchesGlob(name, pattern) {
112
+ if (pattern === '*')
113
+ return true;
114
+ if (pattern.startsWith('*.')) {
115
+ const ext = pattern.slice(1);
116
+ return name.endsWith(ext);
117
+ }
118
+ return name === pattern;
119
+ }
120
+ function recurse(currentPath, segmentIndex, wildcardValues) {
121
+ if (segmentIndex === spec.segments.length) {
122
+ try {
123
+ for (const entry of readdirSync(currentPath, { withFileTypes: true })) {
124
+ if (entry.isDirectory())
125
+ continue;
126
+ const name = entry.name;
127
+ const full = join(currentPath, name);
128
+ if (!matchesGlob(name, spec.file_glob))
129
+ continue;
130
+ const values = [...wildcardValues, name];
131
+ let logical = spec.logical_path_template;
132
+ for (let i = 0; i < values.length; i++) {
133
+ logical = logical.replace(new RegExp(`\\{\\{${i}\\}\\}`, 'g'), values[i]);
134
+ }
135
+ targets.push({
136
+ path: full,
137
+ file_type: spec.file_type,
138
+ content_format: spec.content_format,
139
+ logicalFilePath: logical,
140
+ });
141
+ }
142
+ }
143
+ catch { /* ignore */ }
144
+ return;
145
+ }
146
+ const seg = spec.segments[segmentIndex];
147
+ if (seg.type === 'literal') {
148
+ const next = join(currentPath, seg.name);
149
+ if (existsSync(next))
150
+ recurse(next, segmentIndex + 1, wildcardValues);
151
+ return;
152
+ }
153
+ if (seg.type === 'dir_wildcard') {
154
+ const skips = seg.skip_prefixes?.length ? normalizePathSkipPrefixes(seg.skip_prefixes) : skipPrefixes;
155
+ try {
156
+ for (const entry of readdirSync(currentPath, { withFileTypes: true })) {
157
+ if (!entry.isDirectory())
158
+ continue;
159
+ if (skips.some((p) => entry.name.startsWith(p)))
160
+ continue;
161
+ recurse(join(currentPath, entry.name), segmentIndex + 1, [...wildcardValues, entry.name]);
162
+ }
163
+ }
164
+ catch { /* ignore */ }
165
+ }
166
+ }
167
+ recurse(basePath, 0, []);
168
+ return targets;
169
+ }
170
+ function pushTargetPaths(targets, p, fileType, isDir, collectStyle, contentFormat, dirGlob) {
171
+ if (isDir) {
172
+ targets.push({ path: p, file_type: fileType, isDirectory: true, dir_glob: dirGlob, collect_style: collectStyle, content_format: contentFormat });
173
+ }
174
+ else {
175
+ targets.push({ path: p, file_type: fileType, collect_style: collectStyle, content_format: contentFormat });
176
+ }
177
+ }
178
+ /**
179
+ * Resolve a single (path, type, file_type) from the API to concrete collection targets.
180
+ */
181
+ function resolvePatternToTargets(pathPattern, pathType, fileType, projectRoot, home, pathSkipPrefixes = [], collectStyle, contentFormat, dirGlob, homeRecurseSkipDirs = [], pathResolution, resolveUnderHome = true, absolutePathPrefixes = [], mcpToolGlobSpec, clientPathConstants, pathResolutionSpecs) {
182
+ const norm = pathPattern.replace(/\\/g, '/');
183
+ const targets = [];
184
+ const spec = pathResolution && pathResolutionSpecs?.[pathResolution] ? pathResolutionSpecs[pathResolution] : null;
185
+ if (spec?.type === 'mcp_tool_glob' && mcpToolGlobSpec) {
186
+ return expandGlobFromSpec(mcpToolGlobSpec, home, pathSkipPrefixes);
187
+ }
188
+ if (spec?.type === 'vscdb') {
189
+ if (!clientPathConstants)
190
+ throw new Error(`client_path_constants required to resolve path_resolution="${pathResolution}"`);
191
+ const basePath = getPathByConstantKey(home, clientPathConstants, spec.path_constant_key);
192
+ const sep = spec.fragment_separator;
193
+ const fragment = norm.includes(sep) ? sep + norm.split(sep).slice(1).join(sep) : spec.default_fragment;
194
+ targets.push({ path: basePath + fragment, file_type: fileType, content_format: contentFormat });
195
+ return targets;
196
+ }
197
+ if (spec?.type === 'extensions_cache') {
198
+ if (!clientPathConstants)
199
+ throw new Error(`client_path_constants required to resolve path_resolution="${pathResolution}"`);
200
+ const basePath = getPathByConstantKey(home, clientPathConstants, spec.path_constant_key);
201
+ const suffix = getExtensionsCacheInstalledSuffix(clientPathConstants);
202
+ targets.push({ path: basePath + suffix, file_type: fileType, content_format: contentFormat ?? 'extensions_cache' });
203
+ return targets;
204
+ }
205
+ if (norm.includes('**'))
206
+ return expandRecursiveGlobPathPattern(pathPattern, fileType, home, contentFormat, dirGlob, homeRecurseSkipDirs);
207
+ if (norm.includes('*'))
208
+ return expandGlobPathPattern(pathPattern, fileType, home, projectRoot, contentFormat, dirGlob, absolutePathPrefixes);
209
+ const isDir = pathType === 'dir' || norm.endsWith('/');
210
+ const rel = norm.replace(/\/+$/, '');
211
+ if (norm.startsWith('/') && absolutePathPrefixes.some((prefix) => norm.startsWith(prefix))) {
212
+ pushTargetPaths(targets, norm, fileType, isDir, collectStyle, contentFormat, dirGlob);
213
+ return targets;
214
+ }
215
+ if (norm.startsWith('~/')) {
216
+ pushTargetPaths(targets, join(home, rel.slice(2)), fileType, isDir, collectStyle, contentFormat, dirGlob);
217
+ return targets;
218
+ }
219
+ const stripLeadingSlash = rel.startsWith('/') ? rel.slice(1) : rel;
220
+ pushTargetPaths(targets, join(projectRoot, stripLeadingSlash), fileType, isDir, collectStyle, contentFormat, dirGlob);
221
+ if (stripLeadingSlash.startsWith('.') && resolveUnderHome) {
222
+ pushTargetPaths(targets, join(home, stripLeadingSlash), fileType, isDir, collectStyle, contentFormat, dirGlob);
223
+ }
224
+ return targets;
225
+ }
226
+ export { normalizePathSkipPrefixes, resolvePatternToTargets, expandGlobFromSpec };
227
+ export { expandRecursiveGlobPathPattern, expandGlobPathPattern };
@@ -0,0 +1,69 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ /** Read and parse an MCP config file. Returns null on any error. */
3
+ function readMCPConfig(filePath) {
4
+ try {
5
+ if (!existsSync(filePath))
6
+ return null;
7
+ return JSON.parse(readFileSync(filePath, 'utf-8'));
8
+ }
9
+ catch (error) {
10
+ console.error(`Error reading ${filePath}:`, error instanceof Error ? error.message : String(error));
11
+ return null;
12
+ }
13
+ }
14
+ /** Read and parse a JSON file. Warns on permission errors, returns null on any failure. */
15
+ function readJSONFile(filePath) {
16
+ try {
17
+ if (!existsSync(filePath))
18
+ return null;
19
+ return JSON.parse(readFileSync(filePath, 'utf-8'));
20
+ }
21
+ catch (error) {
22
+ if (error.code === 'EACCES' || error.code === 'EPERM') {
23
+ console.warn(`Permission denied reading ${filePath} (may require elevated permissions)`);
24
+ }
25
+ return null;
26
+ }
27
+ }
28
+ /** Read a text/markdown file. Warns on permission errors, returns null on any failure. */
29
+ function readMarkdownFile(filePath) {
30
+ try {
31
+ if (!existsSync(filePath))
32
+ return null;
33
+ return readFileSync(filePath, 'utf-8');
34
+ }
35
+ catch (error) {
36
+ if (error.code === 'EACCES' || error.code === 'EPERM') {
37
+ console.warn(`Permission denied reading ${filePath} (may require elevated permissions)`);
38
+ }
39
+ return null;
40
+ }
41
+ }
42
+ /** Read installed extensions from Cursor extensions cache file. */
43
+ function readInstalledExtensions(extensionsCachePath) {
44
+ const pathToRead = extensionsCachePath;
45
+ const extensions = [];
46
+ try {
47
+ if (!existsSync(pathToRead))
48
+ return extensions;
49
+ const data = JSON.parse(readFileSync(pathToRead, 'utf-8'));
50
+ if (data.result && Array.isArray(data.result)) {
51
+ for (const ext of data.result) {
52
+ const id = ext.identifier?.id;
53
+ if (id) {
54
+ extensions.push({
55
+ id,
56
+ displayName: ext.manifest?.displayName || ext.manifest?.publisher || '',
57
+ version: ext.manifest?.version || '',
58
+ publisher: ext.manifest?.publisher,
59
+ });
60
+ }
61
+ }
62
+ }
63
+ }
64
+ catch (error) {
65
+ console.warn(`Error reading installed extensions from cache (${pathToRead}):`, error instanceof Error ? error.message : String(error));
66
+ }
67
+ return extensions;
68
+ }
69
+ export { readMCPConfig, readJSONFile, readMarkdownFile, readInstalledExtensions };
@@ -0,0 +1,146 @@
1
+ import { homedir } from 'node:os';
2
+ import { readVSCDBState } from './vscdb_reader.js';
3
+ import { getVscdbPath } from '../paths/path_constants_helpers.js';
4
+ import { getByPath } from '../collection/enrichment_helpers.js';
5
+ import { hookRunLog } from '../runtime/hook_logger.js';
6
+ /** True if path is a vscdb virtual path (db file or db#fragment). Uses backend vscdb_basename and vscdb_entry_specs only. */
7
+ export function isVscdbVirtualPath(filePath, response) {
8
+ if (!response?.vscdb_entry_specs?.length)
9
+ return false;
10
+ const norm = filePath.replace(/\\/g, '/');
11
+ if (norm.includes('#')) {
12
+ const fragment = '#' + norm.split('#').slice(1).join('#');
13
+ if (response.vscdb_entry_specs.some((s) => s.path_suffix === fragment))
14
+ return true;
15
+ }
16
+ if (response.vscdb_basename && (norm.endsWith(response.vscdb_basename) || norm.endsWith(response.vscdb_basename + '#')))
17
+ return true;
18
+ return false;
19
+ }
20
+ /**
21
+ * If path is a vscdb virtual path per API response, read DB and build raw_content + file_type; else return null.
22
+ * Caller uses this so main_runner has no state.vscdb branches.
23
+ */
24
+ export function tryReadVscdbVirtualFile(filePath, response) {
25
+ if (!response?.vscdb_entry_specs?.length)
26
+ return null;
27
+ const norm = filePath.replace(/\\/g, '/');
28
+ const actualPath = norm.split('#')[0];
29
+ const pathSuffix = norm.includes('#') ? '#' + norm.split('#').slice(1).join('#') : '#composerState';
30
+ const spec = response.vscdb_entry_specs.find((s) => s.path_suffix === pathSuffix) ?? response.vscdb_entry_specs.find((s) => s.path_suffix === '#composerState');
31
+ if (!spec)
32
+ return null;
33
+ const isAbsolute = actualPath.startsWith('/') || (actualPath.length >= 2 && actualPath[1] === ':');
34
+ if (!response.client_path_constants)
35
+ throw new Error('client_path_constants required from API response to resolve vscdb path');
36
+ const defaultVscdbPath = getVscdbPath(homedir(), response.client_path_constants);
37
+ const dbPath = isAbsolute ? actualPath : defaultVscdbPath;
38
+ const vscdbState = readVSCDBState(dbPath, response.vscdb_read_queries ?? undefined, response.vscdb_merge_from_composer_state ?? undefined);
39
+ if (!vscdbState)
40
+ return null;
41
+ const rawContent = buildVscdbRawContentFromSpec(vscdbState, spec);
42
+ if (!rawContent)
43
+ return null;
44
+ return { rawContent, fileType: spec.file_type };
45
+ }
46
+ function setNested(obj, dotPath, value) {
47
+ const parts = dotPath.split('.');
48
+ const safe = (k) => k !== '__proto__' && k !== 'constructor' && k !== 'prototype';
49
+ if (!parts.every(safe))
50
+ return;
51
+ let cur = obj;
52
+ for (let i = 0; i < parts.length - 1; i++) {
53
+ const p = parts[i];
54
+ if (!(p in cur) || typeof cur[p] !== 'object')
55
+ cur[p] = Object.create(null);
56
+ cur = cur[p];
57
+ }
58
+ cur[parts[parts.length - 1]] = value;
59
+ }
60
+ function buildRawContent(state, stateKey, value, includeKeys, ts) {
61
+ const raw = { source: 'state.vscdb', extracted_at: ts };
62
+ if (stateKey.includes('.')) {
63
+ setNested(raw, stateKey, value);
64
+ }
65
+ else {
66
+ raw[stateKey] = value;
67
+ }
68
+ if (includeKeys?.length) {
69
+ for (const k of includeKeys) {
70
+ if (k in state && state[k] !== undefined)
71
+ raw[k] = state[k];
72
+ }
73
+ }
74
+ return raw;
75
+ }
76
+ /** Build raw_content for a single state.vscdb fragment from a backend spec. Validation uses spec.value_constraint only. */
77
+ function buildVscdbRawContentFromSpec(state, spec) {
78
+ const value = getByPath(state, spec.state_key);
79
+ if (value === undefined || value === null)
80
+ return null;
81
+ if (spec.value_constraint === 'non_empty_array') {
82
+ if (!Array.isArray(value) || value.length === 0)
83
+ return null;
84
+ }
85
+ if (spec.value_constraint === 'boolean') {
86
+ const okBool = typeof value === 'boolean';
87
+ const okSqliteInt = typeof value === 'number' && (value === 0 || value === 1);
88
+ if (!okBool && !okSqliteInt)
89
+ return null;
90
+ }
91
+ return buildRawContent(state, spec.state_key, value, spec.include_keys, new Date().toISOString());
92
+ }
93
+ /**
94
+ * Build config file entries from state.vscdb using backend-driven specs and read queries.
95
+ * No hardcoded file_type, path fragments, or ItemTable keys; all from API.
96
+ */
97
+ function summarizeComposerStateValue(composerState) {
98
+ if (composerState === null || composerState === undefined || typeof composerState !== 'object' || Array.isArray(composerState)) {
99
+ return `composerState_type=${composerState === null ? 'null' : typeof composerState}`;
100
+ }
101
+ const o = composerState;
102
+ const m4 = o.modes4;
103
+ if (!Array.isArray(m4))
104
+ return `modes4=${m4 === undefined ? 'absent' : typeof m4}`;
105
+ const agent = m4.find((x) => x !== null && typeof x === 'object' && !Array.isArray(x) && x.id === 'agent');
106
+ if (!agent)
107
+ return `modes4_len=${m4.length} agent_row=missing`;
108
+ return `modes4_len=${m4.length} agent_autoRun=${JSON.stringify(agent.autoRun)} agent_fullAutoRun=${JSON.stringify(agent.fullAutoRun)}`;
109
+ }
110
+ /** Exported for main_runner: one-line summary of uploaded #composerState payload (policy full-auto-run input). */
111
+ export function summarizeComposerPayloadForDiagnostics(raw) {
112
+ const inner = summarizeComposerStateValue(raw.composerState);
113
+ const rawKeys = Object.keys(raw)
114
+ .filter((k) => k !== 'source' && k !== 'extracted_at')
115
+ .slice(0, 16);
116
+ return `${inner} payload_keys=[${rawKeys.join(',')}]`;
117
+ }
118
+ function collectVscdbEntries(vscdbPath, specs, readQueries, mergeFromComposerState) {
119
+ if (!specs?.length)
120
+ return [];
121
+ const state = readVSCDBState(vscdbPath, readQueries, mergeFromComposerState);
122
+ if (!state || typeof state !== 'object') {
123
+ hookRunLog(`diag vscdb collect: readVSCDBState returned null/invalid vscdb=${vscdbPath}`);
124
+ return [];
125
+ }
126
+ const sk = Object.keys(state);
127
+ hookRunLog(`diag vscdb collect: readVSCDBState ok vscdb=${vscdbPath} top_keys=[${sk.slice(0, 24).join(',')}] ${summarizeComposerStateValue(state.composerState)}`);
128
+ const entries = [];
129
+ for (const spec of specs) {
130
+ const atPath = getByPath(state, spec.state_key);
131
+ const rawContent = buildVscdbRawContentFromSpec(state, spec);
132
+ if (!rawContent) {
133
+ if (spec.state_key === 'composerState' || spec.path_suffix === '#composerState') {
134
+ hookRunLog(`diag vscdb collect: skipped composer emit value_constraint=${spec.value_constraint ?? 'none'} at_path=${atPath === undefined ? 'undefined' : atPath === null ? 'null' : typeof atPath} path_suffix=${spec.path_suffix}`);
135
+ }
136
+ continue;
137
+ }
138
+ entries.push({
139
+ file_type: spec.file_type,
140
+ file_path: `${vscdbPath}${spec.path_suffix}`,
141
+ raw_content: rawContent,
142
+ });
143
+ }
144
+ return entries;
145
+ }
146
+ export { collectVscdbEntries, buildVscdbRawContentFromSpec };