log-llm-config 1.0.8 → 1.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +10 -1
- package/dist/endpoint_client.js +57 -0
- package/dist/log_config_files.js +702 -10
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -61,7 +61,16 @@ const main = async () => {
|
|
|
61
61
|
return;
|
|
62
62
|
}
|
|
63
63
|
if (args[0] === 'log_config_files') {
|
|
64
|
-
const { main } = await import('./log_config_files.js');
|
|
64
|
+
const { main, logSingleFile } = await import('./log_config_files.js');
|
|
65
|
+
// Check if a file path was provided
|
|
66
|
+
const filePathArg = args.find((arg, idx) => idx > 0 && !arg.startsWith('-') && (arg.includes('/') || arg.endsWith('.json') || arg.endsWith('.md')));
|
|
67
|
+
if (filePathArg) {
|
|
68
|
+
// Log single file
|
|
69
|
+
const success = await logSingleFile(filePathArg);
|
|
70
|
+
process.exit(success ? 0 : 1);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// Otherwise, log all files
|
|
65
74
|
await main();
|
|
66
75
|
return;
|
|
67
76
|
}
|
package/dist/endpoint_client.js
CHANGED
|
@@ -1,6 +1,63 @@
|
|
|
1
1
|
import http from 'node:http';
|
|
2
2
|
import https from 'node:https';
|
|
3
3
|
import { URL } from 'node:url';
|
|
4
|
+
/**
|
|
5
|
+
* GET file collection patterns from the backend (what to look for).
|
|
6
|
+
* No auth required. Returns the complete list of path_pattern + file_type.
|
|
7
|
+
*/
|
|
8
|
+
export const getFileCollectionPatterns = async (apiBaseUrl, timeoutMs = 5000) => {
|
|
9
|
+
const base = apiBaseUrl.replace(/\/+$/, '');
|
|
10
|
+
const fullUrl = base.includes('/endpoint_security')
|
|
11
|
+
? `${base}/api/file-patterns/`.replace(/([^/])\/+/g, '$1/')
|
|
12
|
+
: `${base}/endpoint_security/api/file-patterns/`;
|
|
13
|
+
const url = new URL(fullUrl);
|
|
14
|
+
const isHttps = url.protocol === 'https:';
|
|
15
|
+
let hostname = url.hostname;
|
|
16
|
+
if (hostname === 'localhost' || hostname === '::1') {
|
|
17
|
+
hostname = '127.0.0.1';
|
|
18
|
+
}
|
|
19
|
+
const requestOptions = {
|
|
20
|
+
hostname,
|
|
21
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
22
|
+
path: url.pathname + url.search,
|
|
23
|
+
method: 'GET',
|
|
24
|
+
timeout: timeoutMs,
|
|
25
|
+
};
|
|
26
|
+
const transport = isHttps ? https.request : http.request;
|
|
27
|
+
return new Promise((resolve) => {
|
|
28
|
+
const req = transport(requestOptions, (res) => {
|
|
29
|
+
let responseBody = '';
|
|
30
|
+
res.setEncoding('utf8');
|
|
31
|
+
res.on('data', (chunk) => {
|
|
32
|
+
responseBody += chunk;
|
|
33
|
+
});
|
|
34
|
+
res.on('end', () => {
|
|
35
|
+
if (res.statusCode !== 200 || !responseBody) {
|
|
36
|
+
resolve(null);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const parsed = JSON.parse(responseBody);
|
|
41
|
+
if (Array.isArray(parsed.patterns) && typeof parsed.count === 'number') {
|
|
42
|
+
resolve(parsed);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
resolve(null);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
resolve(null);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
req.on('error', () => resolve(null));
|
|
54
|
+
req.on('timeout', () => {
|
|
55
|
+
req.destroy();
|
|
56
|
+
resolve(null);
|
|
57
|
+
});
|
|
58
|
+
req.end();
|
|
59
|
+
});
|
|
60
|
+
};
|
|
4
61
|
export const postStartupPayload = async (endpointUrl, body, timeoutMs = 5000) => {
|
|
5
62
|
const url = new URL(endpointUrl);
|
|
6
63
|
const isHttps = url.protocol === 'https:';
|
package/dist/log_config_files.js
CHANGED
|
@@ -6,7 +6,7 @@ import { homedir } from 'node:os';
|
|
|
6
6
|
import crypto from 'node:crypto';
|
|
7
7
|
import { execSync } from 'node:child_process';
|
|
8
8
|
import path from 'node:path';
|
|
9
|
-
import { postStartupPayload } from './endpoint_client.js';
|
|
9
|
+
import { getFileCollectionPatterns, postStartupPayload } from './endpoint_client.js';
|
|
10
10
|
const AUTH_KEY_RELATIVE_PATH = path.join('opt-ai-sec', 'management', 'auth_key.txt');
|
|
11
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
12
12
|
const __dirname = dirname(__filename);
|
|
@@ -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'),
|
|
@@ -46,9 +51,21 @@ const JSON_FILE_PATHS = [
|
|
|
46
51
|
path: join(homedir(), '.claude', 'settings.json'),
|
|
47
52
|
file_type: 'claude_settings',
|
|
48
53
|
},
|
|
54
|
+
// Project-level Claude local settings (local overrides)
|
|
55
|
+
{
|
|
56
|
+
path: join(PROJECT_ROOT, '.claude', 'settings.local.json'),
|
|
57
|
+
file_type: 'claude_settings',
|
|
58
|
+
},
|
|
59
|
+
// User-level Claude local settings (local overrides)
|
|
60
|
+
{
|
|
61
|
+
path: join(homedir(), '.claude', 'settings.local.json'),
|
|
62
|
+
file_type: 'claude_settings',
|
|
63
|
+
},
|
|
49
64
|
];
|
|
50
65
|
// VS Code/Cursor state database path
|
|
51
66
|
const VSCDB_PATH = join(homedir(), 'Library', 'Application Support', 'Cursor', 'User', 'globalStorage', 'state.vscdb');
|
|
67
|
+
// Cursor extensions cache file path
|
|
68
|
+
const EXTENSIONS_CACHE_PATH = join(homedir(), 'Library', 'Application Support', 'Cursor', 'CachedProfilesData', '__default__profile__', 'extensions.user.cache');
|
|
52
69
|
// Claude configuration file paths
|
|
53
70
|
const CLAUDE_FILE_PATHS = [
|
|
54
71
|
// Project Root
|
|
@@ -69,14 +86,252 @@ const CLAUDE_FILE_PATHS = [
|
|
|
69
86
|
];
|
|
70
87
|
// Claude rules directory
|
|
71
88
|
const CLAUDE_RULES_DIR = join(homedir(), '.claude', 'rules');
|
|
89
|
+
// Claude subagents directories
|
|
90
|
+
const CLAUDE_AGENTS_PROJECT_DIR = join(PROJECT_ROOT, '.claude', 'agents');
|
|
91
|
+
const CLAUDE_AGENTS_USER_DIR = join(homedir(), '.claude', 'agents');
|
|
72
92
|
// Cursor rules directory (project-level)
|
|
73
93
|
const CURSOR_RULES_DIR = join(PROJECT_ROOT, '.cursor', 'rules');
|
|
94
|
+
// Cursor commands directories
|
|
95
|
+
const CURSOR_COMMANDS_PROJECT_DIR = join(PROJECT_ROOT, '.cursor', 'commands');
|
|
96
|
+
const CURSOR_COMMANDS_USER_DIR = join(homedir(), '.cursor', 'commands');
|
|
97
|
+
// Cursor subagents directory (project-level)
|
|
98
|
+
const CURSOR_AGENTS_DIR = join(PROJECT_ROOT, '.cursor', 'agents');
|
|
74
99
|
// Plugin directories to scan
|
|
75
100
|
// Claude plugins can be in various locations (project, user home, etc.)
|
|
76
101
|
const PLUGIN_SEARCH_DIRS = [
|
|
77
102
|
PROJECT_ROOT, // Project-level plugins
|
|
78
103
|
join(homedir(), '.claude', 'plugins'), // User-level plugins (if this becomes a standard location)
|
|
79
104
|
];
|
|
105
|
+
/** Glob(s) for directory scans by file_type. First match used if array. */
|
|
106
|
+
const DIR_GLOB_BY_FILE_TYPE = {
|
|
107
|
+
cursor_command: '*.md',
|
|
108
|
+
cursor_rule: '*.md',
|
|
109
|
+
claude_rule: '*.md',
|
|
110
|
+
claude_subagent: '*.md',
|
|
111
|
+
cursor_subagent: '*.md',
|
|
112
|
+
claude_skill: '*.md',
|
|
113
|
+
codex_skill: '*.md',
|
|
114
|
+
claude_plugin_command: '*.md',
|
|
115
|
+
claude_plugin_manifest: 'plugin.json',
|
|
116
|
+
claude_plugin_hooks: 'hooks.json',
|
|
117
|
+
claude_plugin_mcp: '.mcp.json',
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* Resolve a single (path_pattern, file_type) from the API to concrete collection targets.
|
|
121
|
+
*/
|
|
122
|
+
function resolvePatternToTargets(pathPattern, fileType, projectRoot, home) {
|
|
123
|
+
const norm = pathPattern.replace(/\\/g, '/');
|
|
124
|
+
const targets = [];
|
|
125
|
+
// Special: state.vscdb (any fragment) -> single target; we read and split in collection
|
|
126
|
+
if (norm.includes('state.vscdb')) {
|
|
127
|
+
targets.push({ path: VSCDB_PATH, file_type: fileType });
|
|
128
|
+
return targets;
|
|
129
|
+
}
|
|
130
|
+
// Special: extensions.user.cache#installedExtensions
|
|
131
|
+
if (norm.includes('extensions.user.cache') && norm.includes('installedExtensions')) {
|
|
132
|
+
targets.push({ path: `${EXTENSIONS_CACHE_PATH}#installedExtensions`, file_type: 'cursor_extensions' });
|
|
133
|
+
return targets;
|
|
134
|
+
}
|
|
135
|
+
const isDir = norm.endsWith('/');
|
|
136
|
+
const rel = norm.replace(/\/+$/, '');
|
|
137
|
+
const push = (p) => {
|
|
138
|
+
if (isDir) {
|
|
139
|
+
const glob = DIR_GLOB_BY_FILE_TYPE[fileType];
|
|
140
|
+
targets.push({
|
|
141
|
+
path: p,
|
|
142
|
+
file_type: fileType,
|
|
143
|
+
isDirectory: true,
|
|
144
|
+
glob: Array.isArray(glob) ? glob[0] : glob,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
targets.push({ path: p, file_type: fileType });
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
// Absolute path (real absolute, not /.cursor/...)
|
|
152
|
+
if (norm.startsWith('/') && (norm.startsWith('/Library') || norm.startsWith('/Users') || norm.startsWith('/home') || norm.startsWith('/etc'))) {
|
|
153
|
+
push(norm);
|
|
154
|
+
return targets;
|
|
155
|
+
}
|
|
156
|
+
// Home-relative
|
|
157
|
+
if (norm.startsWith('~/')) {
|
|
158
|
+
push(join(home, rel.slice(2)));
|
|
159
|
+
return targets;
|
|
160
|
+
}
|
|
161
|
+
// Project- or both-relative: /.cursor/..., .cursor/..., CLAUDE.md, etc.
|
|
162
|
+
const stripLeadingSlash = rel.startsWith('/') ? rel.slice(1) : rel;
|
|
163
|
+
push(join(projectRoot, stripLeadingSlash));
|
|
164
|
+
if (!stripLeadingSlash.startsWith('.')) {
|
|
165
|
+
// e.g. CLAUDE.md at project root only for first push; also at ~/.claude/CLAUDE.md
|
|
166
|
+
if (stripLeadingSlash === 'CLAUDE.md' || stripLeadingSlash === 'CLAUDE.local.md') {
|
|
167
|
+
push(join(home, '.claude', stripLeadingSlash));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
push(join(home, stripLeadingSlash));
|
|
172
|
+
}
|
|
173
|
+
return targets;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Collect config files by resolving API patterns to paths and reading existing files.
|
|
177
|
+
* Reuses readMCPConfig, readJSONFile, readMarkdownFile, readVSCDBState, readInstalledExtensions and directory scan helpers.
|
|
178
|
+
*/
|
|
179
|
+
function collectConfigFilesFromPatterns(patterns, projectRoot) {
|
|
180
|
+
const home = homedir();
|
|
181
|
+
const seenPaths = new Set();
|
|
182
|
+
const targets = [];
|
|
183
|
+
for (const { path_pattern, file_type } of patterns) {
|
|
184
|
+
for (const t of resolvePatternToTargets(path_pattern, file_type, projectRoot, home)) {
|
|
185
|
+
const key = `${t.path}\t${t.file_type}`;
|
|
186
|
+
if (seenPaths.has(key))
|
|
187
|
+
continue;
|
|
188
|
+
seenPaths.add(key);
|
|
189
|
+
targets.push(t);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const configFiles = [];
|
|
193
|
+
const handledSpecialPaths = new Set();
|
|
194
|
+
for (const t of targets) {
|
|
195
|
+
if (t.isDirectory) {
|
|
196
|
+
if (!existsSync(t.path))
|
|
197
|
+
continue;
|
|
198
|
+
try {
|
|
199
|
+
const entries = readdirSync(t.path, { withFileTypes: true });
|
|
200
|
+
const glob = t.glob || '*.md';
|
|
201
|
+
const matchName = (name) => glob.startsWith('*') ? name.endsWith(glob.slice(1)) : name === glob;
|
|
202
|
+
for (const entry of entries) {
|
|
203
|
+
if (!entry.isFile())
|
|
204
|
+
continue;
|
|
205
|
+
if (!matchName(entry.name))
|
|
206
|
+
continue;
|
|
207
|
+
const fullPath = join(t.path, entry.name);
|
|
208
|
+
const content = readMarkdownFile(fullPath) ?? readJSONFile(fullPath);
|
|
209
|
+
if (content !== null) {
|
|
210
|
+
const raw = typeof content === 'string' ? { content, source: 'file' } : content;
|
|
211
|
+
configFiles.push({
|
|
212
|
+
file_type: t.file_type,
|
|
213
|
+
file_path: fullPath,
|
|
214
|
+
raw_content: raw,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// cursor_rule: also support subdirs with RULE.md
|
|
219
|
+
if (t.file_type === 'cursor_rule') {
|
|
220
|
+
for (const entry of entries) {
|
|
221
|
+
if (!entry.isDirectory())
|
|
222
|
+
continue;
|
|
223
|
+
const ruleMd = join(t.path, entry.name, 'RULE.md');
|
|
224
|
+
if (existsSync(ruleMd)) {
|
|
225
|
+
const content = readMarkdownFile(ruleMd);
|
|
226
|
+
if (content !== null) {
|
|
227
|
+
configFiles.push({
|
|
228
|
+
file_type: t.file_type,
|
|
229
|
+
file_path: ruleMd,
|
|
230
|
+
raw_content: { content, source: 'cursor_rule_file' },
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
console.warn(`Error reading directory ${t.path}:`, err instanceof Error ? err.message : String(err));
|
|
239
|
+
}
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
// Single file or special
|
|
243
|
+
if (t.path.includes('#installedExtensions')) {
|
|
244
|
+
if (handledSpecialPaths.has(EXTENSIONS_CACHE_PATH))
|
|
245
|
+
continue;
|
|
246
|
+
handledSpecialPaths.add(EXTENSIONS_CACHE_PATH);
|
|
247
|
+
const installed = readInstalledExtensions();
|
|
248
|
+
if (installed.length > 0) {
|
|
249
|
+
configFiles.push({
|
|
250
|
+
file_type: 'cursor_extensions',
|
|
251
|
+
file_path: `${EXTENSIONS_CACHE_PATH}#installedExtensions`,
|
|
252
|
+
raw_content: {
|
|
253
|
+
installedExtensions: installed,
|
|
254
|
+
source: 'extensions.user.cache',
|
|
255
|
+
extracted_at: new Date().toISOString(),
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (t.path === VSCDB_PATH) {
|
|
262
|
+
if (handledSpecialPaths.has(VSCDB_PATH))
|
|
263
|
+
continue;
|
|
264
|
+
handledSpecialPaths.add(VSCDB_PATH);
|
|
265
|
+
const vscdbState = readVSCDBState();
|
|
266
|
+
if (vscdbState) {
|
|
267
|
+
if (vscdbState.composerState) {
|
|
268
|
+
configFiles.push({
|
|
269
|
+
file_type: 'vscode_settings',
|
|
270
|
+
file_path: `${VSCDB_PATH}#composerState`,
|
|
271
|
+
raw_content: {
|
|
272
|
+
composerState: vscdbState.composerState,
|
|
273
|
+
source: 'state.vscdb',
|
|
274
|
+
extracted_at: new Date().toISOString(),
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
const chat = vscdbState.chat;
|
|
279
|
+
if (chat?.tools) {
|
|
280
|
+
configFiles.push({
|
|
281
|
+
file_type: 'vscode_settings',
|
|
282
|
+
file_path: `${VSCDB_PATH}#chat.tools`,
|
|
283
|
+
raw_content: {
|
|
284
|
+
chat: { tools: chat.tools },
|
|
285
|
+
source: 'state.vscdb',
|
|
286
|
+
extracted_at: new Date().toISOString(),
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
const extRec = vscdbState.extensionsAssistant;
|
|
291
|
+
if (extRec?.recommendations?.length) {
|
|
292
|
+
configFiles.push({
|
|
293
|
+
file_type: 'cursor_extensions',
|
|
294
|
+
file_path: `${VSCDB_PATH}#extensionsAssistant.recommendations`,
|
|
295
|
+
raw_content: {
|
|
296
|
+
extensionsAssistant: { recommendations: extRec.recommendations },
|
|
297
|
+
source: 'state.vscdb',
|
|
298
|
+
extracted_at: new Date().toISOString(),
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
const ext = vscdbState.extensions;
|
|
303
|
+
if (ext?.trustedPublishers?.length) {
|
|
304
|
+
configFiles.push({
|
|
305
|
+
file_type: 'cursor_extensions',
|
|
306
|
+
file_path: `${VSCDB_PATH}#extensions.trustedPublishers`,
|
|
307
|
+
raw_content: {
|
|
308
|
+
extensions: { trustedPublishers: ext.trustedPublishers },
|
|
309
|
+
source: 'state.vscdb',
|
|
310
|
+
extracted_at: new Date().toISOString(),
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
if (!existsSync(t.path))
|
|
318
|
+
continue;
|
|
319
|
+
const isJson = t.path.endsWith('.json') ||
|
|
320
|
+
t.file_type === 'mcp_config' ||
|
|
321
|
+
t.file_type === 'cursor_hooks' ||
|
|
322
|
+
t.file_type === 'claude_settings';
|
|
323
|
+
const content = isJson ? readJSONFile(t.path) ?? readMCPConfig(t.path) : readMarkdownFile(t.path);
|
|
324
|
+
if (content !== null) {
|
|
325
|
+
const raw = typeof content === 'string' ? { content, source: 'file' } : content;
|
|
326
|
+
configFiles.push({
|
|
327
|
+
file_type: t.file_type,
|
|
328
|
+
file_path: t.path,
|
|
329
|
+
raw_content: raw,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return configFiles;
|
|
334
|
+
}
|
|
80
335
|
/**
|
|
81
336
|
* Read and parse an MCP config file
|
|
82
337
|
*/
|
|
@@ -183,6 +438,90 @@ function getClaudeRuleFiles() {
|
|
|
183
438
|
}
|
|
184
439
|
return files;
|
|
185
440
|
}
|
|
441
|
+
/**
|
|
442
|
+
* Get all command files from Cursor commands directories
|
|
443
|
+
* Collects from both project-level (.cursor/commands/) and user-level (~/.cursor/commands/)
|
|
444
|
+
*/
|
|
445
|
+
function getCursorCommandFiles() {
|
|
446
|
+
const files = [];
|
|
447
|
+
// Collect from project-level commands directory
|
|
448
|
+
try {
|
|
449
|
+
if (existsSync(CURSOR_COMMANDS_PROJECT_DIR)) {
|
|
450
|
+
const entries = readdirSync(CURSOR_COMMANDS_PROJECT_DIR, { withFileTypes: true });
|
|
451
|
+
for (const entry of entries) {
|
|
452
|
+
if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
453
|
+
files.push({
|
|
454
|
+
path: join(CURSOR_COMMANDS_PROJECT_DIR, entry.name),
|
|
455
|
+
file_type: 'cursor_command',
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
catch (error) {
|
|
462
|
+
console.warn(`Error reading Cursor project commands directory ${CURSOR_COMMANDS_PROJECT_DIR}:`, error instanceof Error ? error.message : String(error));
|
|
463
|
+
}
|
|
464
|
+
// Collect from user-level commands directory
|
|
465
|
+
try {
|
|
466
|
+
if (existsSync(CURSOR_COMMANDS_USER_DIR)) {
|
|
467
|
+
const entries = readdirSync(CURSOR_COMMANDS_USER_DIR, { withFileTypes: true });
|
|
468
|
+
for (const entry of entries) {
|
|
469
|
+
if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
470
|
+
files.push({
|
|
471
|
+
path: join(CURSOR_COMMANDS_USER_DIR, entry.name),
|
|
472
|
+
file_type: 'cursor_command',
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
catch (error) {
|
|
479
|
+
console.warn(`Error reading Cursor user commands directory ${CURSOR_COMMANDS_USER_DIR}:`, error instanceof Error ? error.message : String(error));
|
|
480
|
+
}
|
|
481
|
+
return files;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Get all subagent files from Claude agents directories
|
|
485
|
+
* Collects from both project-level (.claude/agents/) and user-level (~/.claude/agents/)
|
|
486
|
+
*/
|
|
487
|
+
function getClaudeSubagentFiles() {
|
|
488
|
+
const files = [];
|
|
489
|
+
// Collect from project-level agents directory
|
|
490
|
+
try {
|
|
491
|
+
if (existsSync(CLAUDE_AGENTS_PROJECT_DIR)) {
|
|
492
|
+
const entries = readdirSync(CLAUDE_AGENTS_PROJECT_DIR, { withFileTypes: true });
|
|
493
|
+
for (const entry of entries) {
|
|
494
|
+
if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
495
|
+
files.push({
|
|
496
|
+
path: join(CLAUDE_AGENTS_PROJECT_DIR, entry.name),
|
|
497
|
+
file_type: 'claude_subagent',
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
catch (error) {
|
|
504
|
+
console.warn(`Error reading Claude project agents directory ${CLAUDE_AGENTS_PROJECT_DIR}:`, error instanceof Error ? error.message : String(error));
|
|
505
|
+
}
|
|
506
|
+
// Collect from user-level agents directory
|
|
507
|
+
try {
|
|
508
|
+
if (existsSync(CLAUDE_AGENTS_USER_DIR)) {
|
|
509
|
+
const entries = readdirSync(CLAUDE_AGENTS_USER_DIR, { withFileTypes: true });
|
|
510
|
+
for (const entry of entries) {
|
|
511
|
+
if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
512
|
+
files.push({
|
|
513
|
+
path: join(CLAUDE_AGENTS_USER_DIR, entry.name),
|
|
514
|
+
file_type: 'claude_subagent',
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
catch (error) {
|
|
521
|
+
console.warn(`Error reading Claude user agents directory ${CLAUDE_AGENTS_USER_DIR}:`, error instanceof Error ? error.message : String(error));
|
|
522
|
+
}
|
|
523
|
+
return files;
|
|
524
|
+
}
|
|
186
525
|
/**
|
|
187
526
|
* Get all rule files from Cursor rules directory
|
|
188
527
|
* Supports new format (.cursor/rules/rule-name/RULE.md) and legacy format (.cursor/rules/*.mdc or *.md)
|
|
@@ -395,6 +734,29 @@ function getPluginHookFiles(pluginDir) {
|
|
|
395
734
|
/**
|
|
396
735
|
* Get MCP config files from a plugin directory (.mcp.json)
|
|
397
736
|
*/
|
|
737
|
+
function getPluginAgentFiles(pluginDir) {
|
|
738
|
+
const files = [];
|
|
739
|
+
// pluginDir is the directory containing .claude-plugin, so agents are at pluginDir/.claude-plugin/agents/*.md
|
|
740
|
+
const agentsDir = join(pluginDir, '.claude-plugin', 'agents');
|
|
741
|
+
try {
|
|
742
|
+
if (!existsSync(agentsDir)) {
|
|
743
|
+
return files;
|
|
744
|
+
}
|
|
745
|
+
const entries = readdirSync(agentsDir, { withFileTypes: true });
|
|
746
|
+
for (const entry of entries) {
|
|
747
|
+
if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
748
|
+
files.push({
|
|
749
|
+
path: join(agentsDir, entry.name),
|
|
750
|
+
file_type: 'claude_subagent',
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
catch (error) {
|
|
756
|
+
console.warn(`Error reading plugin agents directory ${agentsDir}:`, error instanceof Error ? error.message : String(error));
|
|
757
|
+
}
|
|
758
|
+
return files;
|
|
759
|
+
}
|
|
398
760
|
function getPluginMcpFiles(pluginDir) {
|
|
399
761
|
const files = [];
|
|
400
762
|
// pluginDir is the directory containing .claude-plugin, so MCP config is at pluginDir/.claude-plugin/.mcp.json
|
|
@@ -439,10 +801,12 @@ function getSubdirectoryClaudeFiles(projectRoot) {
|
|
|
439
801
|
/**
|
|
440
802
|
* Read state data from Cursor's state.vscdb SQLite database
|
|
441
803
|
* Extracts composerState, extensionsAssistant, and extensions data
|
|
804
|
+
* @param dbPath Optional path to state.vscdb file. If not provided, uses default VSCDB_PATH.
|
|
442
805
|
*/
|
|
443
|
-
function readVSCDBState() {
|
|
806
|
+
function readVSCDBState(dbPath) {
|
|
444
807
|
try {
|
|
445
|
-
|
|
808
|
+
const actualPath = dbPath || VSCDB_PATH;
|
|
809
|
+
if (!existsSync(actualPath)) {
|
|
446
810
|
return null;
|
|
447
811
|
}
|
|
448
812
|
// Check if sqlite3 is available
|
|
@@ -460,7 +824,7 @@ function readVSCDBState() {
|
|
|
460
824
|
const escapedComposerKey = composerStateKey.replace(/'/g, "''");
|
|
461
825
|
const composerQuery = `SELECT value FROM ItemTable WHERE key='${escapedComposerKey}'`;
|
|
462
826
|
try {
|
|
463
|
-
const composerResult = execSync(`sqlite3 "${
|
|
827
|
+
const composerResult = execSync(`sqlite3 "${actualPath}" "${composerQuery}"`, {
|
|
464
828
|
encoding: 'utf8',
|
|
465
829
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
466
830
|
}).trim();
|
|
@@ -492,7 +856,7 @@ function readVSCDBState() {
|
|
|
492
856
|
try {
|
|
493
857
|
const escapedKey = key.replace(/'/g, "''");
|
|
494
858
|
const query = `SELECT value FROM ItemTable WHERE key='${escapedKey}'`;
|
|
495
|
-
const result = execSync(`sqlite3 "${
|
|
859
|
+
const result = execSync(`sqlite3 "${actualPath}" "${query}"`, {
|
|
496
860
|
encoding: 'utf8',
|
|
497
861
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
498
862
|
}).trim();
|
|
@@ -529,7 +893,7 @@ function readVSCDBState() {
|
|
|
529
893
|
try {
|
|
530
894
|
const escapedKey = key.replace(/'/g, "''");
|
|
531
895
|
const query = `SELECT value FROM ItemTable WHERE key='${escapedKey}'`;
|
|
532
|
-
const result = execSync(`sqlite3 "${
|
|
896
|
+
const result = execSync(`sqlite3 "${actualPath}" "${query}"`, {
|
|
533
897
|
encoding: 'utf8',
|
|
534
898
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
535
899
|
}).trim();
|
|
@@ -557,6 +921,30 @@ function readVSCDBState() {
|
|
|
557
921
|
// Continue to next key
|
|
558
922
|
}
|
|
559
923
|
}
|
|
924
|
+
// Query for chat.tools data (for chat.tools.autoApprove setting)
|
|
925
|
+
const chatToolsKey = "chat.tools";
|
|
926
|
+
try {
|
|
927
|
+
const escapedKey = chatToolsKey.replace(/'/g, "''");
|
|
928
|
+
const query = `SELECT value FROM ItemTable WHERE key='${escapedKey}'`;
|
|
929
|
+
const result = execSync(`sqlite3 "${actualPath}" "${query}"`, {
|
|
930
|
+
encoding: 'utf8',
|
|
931
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
932
|
+
}).trim();
|
|
933
|
+
if (result && result !== '') {
|
|
934
|
+
try {
|
|
935
|
+
const parsed = JSON.parse(result);
|
|
936
|
+
if (parsed && typeof parsed === 'object') {
|
|
937
|
+
stateData.chat = { tools: parsed };
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
catch (parseError) {
|
|
941
|
+
// Continue if parse fails
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
catch (error) {
|
|
946
|
+
// Continue if query fails
|
|
947
|
+
}
|
|
560
948
|
// If we found composerState, also check if extensionsAssistant/extensions are nested within it
|
|
561
949
|
if (stateData.composerState && typeof stateData.composerState === 'object') {
|
|
562
950
|
const composerState = stateData.composerState;
|
|
@@ -569,7 +957,7 @@ function readVSCDBState() {
|
|
|
569
957
|
stateData.extensions = composerState.extensions;
|
|
570
958
|
}
|
|
571
959
|
}
|
|
572
|
-
// Return the combined state data (composerState, extensionsAssistant, extensions)
|
|
960
|
+
// Return the combined state data (composerState, extensionsAssistant, extensions, chat, installedExtensions)
|
|
573
961
|
// If we have any data, return it; otherwise return null
|
|
574
962
|
if (Object.keys(stateData).length > 0) {
|
|
575
963
|
return stateData;
|
|
@@ -581,6 +969,37 @@ function readVSCDBState() {
|
|
|
581
969
|
return null;
|
|
582
970
|
}
|
|
583
971
|
}
|
|
972
|
+
/**
|
|
973
|
+
* Read installed extensions from Cursor extensions cache file.
|
|
974
|
+
* Original cache has: result[].identifier.id, result[].manifest.displayName, .version, .publisher.
|
|
975
|
+
* We collect publisher so UI can distinguish same displayName (e.g. "Python" from Microsoft vs Anysphere) later.
|
|
976
|
+
* Returns array: [{ id, displayName, version, publisher }, ...]
|
|
977
|
+
*/
|
|
978
|
+
function readInstalledExtensions() {
|
|
979
|
+
const extensions = [];
|
|
980
|
+
try {
|
|
981
|
+
if (!existsSync(EXTENSIONS_CACHE_PATH)) {
|
|
982
|
+
return extensions;
|
|
983
|
+
}
|
|
984
|
+
const content = readFileSync(EXTENSIONS_CACHE_PATH, 'utf-8');
|
|
985
|
+
const data = JSON.parse(content);
|
|
986
|
+
if (data.result && Array.isArray(data.result)) {
|
|
987
|
+
for (const ext of data.result) {
|
|
988
|
+
const id = ext.identifier?.id;
|
|
989
|
+
const displayName = ext.manifest?.displayName || ext.manifest?.publisher || '';
|
|
990
|
+
const version = ext.manifest?.version || '';
|
|
991
|
+
const publisher = ext.manifest?.publisher;
|
|
992
|
+
if (id) {
|
|
993
|
+
extensions.push({ id, displayName, version, publisher });
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
catch (error) {
|
|
999
|
+
console.warn(`Error reading installed extensions from cache:`, error instanceof Error ? error.message : String(error));
|
|
1000
|
+
}
|
|
1001
|
+
return extensions;
|
|
1002
|
+
}
|
|
584
1003
|
/**
|
|
585
1004
|
* Collect all configuration files
|
|
586
1005
|
*/
|
|
@@ -632,6 +1051,22 @@ function collectConfigFiles() {
|
|
|
632
1051
|
},
|
|
633
1052
|
});
|
|
634
1053
|
}
|
|
1054
|
+
// Store chat.tools as vscode_settings (for chat.tools.autoApprove policy)
|
|
1055
|
+
// Send as separate file entry to match policy engine expectations
|
|
1056
|
+
const chat = vscdbState.chat;
|
|
1057
|
+
if (chat?.tools) {
|
|
1058
|
+
configFiles.push({
|
|
1059
|
+
file_type: 'vscode_settings',
|
|
1060
|
+
file_path: `${VSCDB_PATH}#chat.tools`, // Use fragment to distinguish
|
|
1061
|
+
raw_content: {
|
|
1062
|
+
chat: {
|
|
1063
|
+
tools: chat.tools,
|
|
1064
|
+
},
|
|
1065
|
+
source: 'state.vscdb',
|
|
1066
|
+
extracted_at: new Date().toISOString(),
|
|
1067
|
+
},
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
635
1070
|
// Store extensionsAssistant.recommendations as cursor_extensions
|
|
636
1071
|
// (same type as .vscode/extensions.json recommendations)
|
|
637
1072
|
// Structure must match evaluation plan path: extensionsAssistant.recommendations
|
|
@@ -666,6 +1101,23 @@ function collectConfigFiles() {
|
|
|
666
1101
|
});
|
|
667
1102
|
}
|
|
668
1103
|
}
|
|
1104
|
+
// Store installed extensions as cursor_extensions
|
|
1105
|
+
// Read from extensions cache file (more reliable than state.vscdb)
|
|
1106
|
+
// Format: [{ id, displayName, version }, ...]
|
|
1107
|
+
// This is collected separately from state.vscdb since it's in a different file
|
|
1108
|
+
const installedExtensions = readInstalledExtensions();
|
|
1109
|
+
if (installedExtensions.length > 0) {
|
|
1110
|
+
console.log(`Found ${installedExtensions.length} installed extension(s) at: ${EXTENSIONS_CACHE_PATH}`);
|
|
1111
|
+
configFiles.push({
|
|
1112
|
+
file_type: 'cursor_extensions',
|
|
1113
|
+
file_path: `${EXTENSIONS_CACHE_PATH}#installedExtensions`, // Use fragment to distinguish
|
|
1114
|
+
raw_content: {
|
|
1115
|
+
installedExtensions: installedExtensions,
|
|
1116
|
+
source: 'extensions.user.cache',
|
|
1117
|
+
extracted_at: new Date().toISOString(),
|
|
1118
|
+
},
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
669
1121
|
// Read Claude configuration files (project root, local overrides, home directory)
|
|
670
1122
|
for (const { path, file_type } of CLAUDE_FILE_PATHS) {
|
|
671
1123
|
try {
|
|
@@ -749,6 +1201,48 @@ function collectConfigFiles() {
|
|
|
749
1201
|
console.warn(`Error reading ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
750
1202
|
}
|
|
751
1203
|
}
|
|
1204
|
+
// Read Claude subagent files from .claude/agents/*.md and ~/.claude/agents/*.md
|
|
1205
|
+
const claudeSubagentFiles = getClaudeSubagentFiles();
|
|
1206
|
+
for (const { path, file_type } of claudeSubagentFiles) {
|
|
1207
|
+
try {
|
|
1208
|
+
const markdownContent = readMarkdownFile(path);
|
|
1209
|
+
if (markdownContent !== null) { // Log even if empty
|
|
1210
|
+
console.log(`Found ${file_type} at: ${path}`);
|
|
1211
|
+
configFiles.push({
|
|
1212
|
+
file_type,
|
|
1213
|
+
file_path: path,
|
|
1214
|
+
raw_content: {
|
|
1215
|
+
content: markdownContent,
|
|
1216
|
+
source: 'claude_subagent_file',
|
|
1217
|
+
},
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
catch (error) {
|
|
1222
|
+
console.warn(`Error reading ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
// Read Cursor command files from .cursor/commands/*.md and ~/.cursor/commands/*.md
|
|
1226
|
+
const cursorCommandFiles = getCursorCommandFiles();
|
|
1227
|
+
for (const { path, file_type } of cursorCommandFiles) {
|
|
1228
|
+
try {
|
|
1229
|
+
const markdownContent = readMarkdownFile(path);
|
|
1230
|
+
if (markdownContent !== null) { // Log even if empty
|
|
1231
|
+
console.log(`Found ${file_type} at: ${path}`);
|
|
1232
|
+
configFiles.push({
|
|
1233
|
+
file_type,
|
|
1234
|
+
file_path: path,
|
|
1235
|
+
raw_content: {
|
|
1236
|
+
content: markdownContent,
|
|
1237
|
+
source: 'cursor_command_file',
|
|
1238
|
+
},
|
|
1239
|
+
});
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
catch (error) {
|
|
1243
|
+
console.warn(`Error reading ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
752
1246
|
// Read Cursor rule files from .cursor/rules/ (new format: folders with RULE.md, legacy: .mdc/.md files)
|
|
753
1247
|
const cursorRuleFiles = getCursorRuleFiles();
|
|
754
1248
|
for (const { path, file_type } of cursorRuleFiles) {
|
|
@@ -884,6 +1378,27 @@ function collectConfigFiles() {
|
|
|
884
1378
|
console.warn(`Error reading plugin hooks ${hookPath}:`, error instanceof Error ? error.message : String(error));
|
|
885
1379
|
}
|
|
886
1380
|
}
|
|
1381
|
+
// Collect plugin agent files (agents/*.md)
|
|
1382
|
+
const agentFiles = getPluginAgentFiles(pluginDir);
|
|
1383
|
+
for (const { path: agentPath, file_type: agentFileType } of agentFiles) {
|
|
1384
|
+
try {
|
|
1385
|
+
const agentContent = readMarkdownFile(agentPath);
|
|
1386
|
+
if (agentContent !== null) {
|
|
1387
|
+
configFiles.push({
|
|
1388
|
+
file_type: agentFileType,
|
|
1389
|
+
file_path: agentPath,
|
|
1390
|
+
raw_content: {
|
|
1391
|
+
content: agentContent,
|
|
1392
|
+
source: 'claude_plugin_agent_file',
|
|
1393
|
+
pluginDir: pluginDir,
|
|
1394
|
+
},
|
|
1395
|
+
});
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
catch (error) {
|
|
1399
|
+
console.warn(`Error reading plugin agent ${agentPath}:`, error instanceof Error ? error.message : String(error));
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
887
1402
|
// Collect plugin MCP config files (.mcp.json)
|
|
888
1403
|
const mcpFiles = getPluginMcpFiles(pluginDir);
|
|
889
1404
|
for (const { path: mcpPath, file_type: mcpFileType } of mcpFiles) {
|
|
@@ -1204,17 +1719,194 @@ async function ensureAuthentication(hardwareUuid) {
|
|
|
1204
1719
|
throw new Error(`Failed to authenticate: ${error instanceof Error ? error.message : String(error)}`);
|
|
1205
1720
|
}
|
|
1206
1721
|
}
|
|
1722
|
+
/**
|
|
1723
|
+
* Determine file type from file path (matches backend logic)
|
|
1724
|
+
*/
|
|
1725
|
+
function determineFileTypeFromPath(filePath) {
|
|
1726
|
+
if (!filePath) {
|
|
1727
|
+
return null;
|
|
1728
|
+
}
|
|
1729
|
+
// Normalize path (handle both forward and backslashes)
|
|
1730
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
1731
|
+
// Pattern matching (order matters - most specific first)
|
|
1732
|
+
// These patterns match the backend's file_type_detector.py
|
|
1733
|
+
// MCP configs
|
|
1734
|
+
if (normalizedPath.includes('/.cursor/mcp.json') ||
|
|
1735
|
+
normalizedPath.includes('mcp.json') ||
|
|
1736
|
+
normalizedPath.includes('.mcp.json')) {
|
|
1737
|
+
return 'mcp_config';
|
|
1738
|
+
}
|
|
1739
|
+
// Claude settings (most specific first)
|
|
1740
|
+
if (normalizedPath.includes('/Library/Application Support/ClaudeCode/managed-settings.json')) {
|
|
1741
|
+
return 'claude_settings';
|
|
1742
|
+
}
|
|
1743
|
+
if (normalizedPath.includes('.claude/settings.local.json') ||
|
|
1744
|
+
normalizedPath.includes('~/.claude/settings.local.json') ||
|
|
1745
|
+
normalizedPath.includes('/.claude/settings.local.json')) {
|
|
1746
|
+
return 'claude_settings';
|
|
1747
|
+
}
|
|
1748
|
+
if (normalizedPath.includes('.claude/settings.json') ||
|
|
1749
|
+
normalizedPath.includes('~/.config/claude/settings.json') ||
|
|
1750
|
+
normalizedPath.includes('/.config/claude/settings.json')) {
|
|
1751
|
+
return 'claude_settings';
|
|
1752
|
+
}
|
|
1753
|
+
// Cursor hooks
|
|
1754
|
+
if (normalizedPath.includes('.cursor/hooks.json') ||
|
|
1755
|
+
normalizedPath.includes('/Library/Application Support/Cursor/hooks.json')) {
|
|
1756
|
+
return 'cursor_hooks';
|
|
1757
|
+
}
|
|
1758
|
+
// Claude rule config
|
|
1759
|
+
if (normalizedPath.includes('CLAUDE.md') || normalizedPath.includes('CLAUDE.local.md')) {
|
|
1760
|
+
return 'claude_rule_config';
|
|
1761
|
+
}
|
|
1762
|
+
// Claude rules
|
|
1763
|
+
if (normalizedPath.includes('.claude/rules/')) {
|
|
1764
|
+
return 'claude_rule';
|
|
1765
|
+
}
|
|
1766
|
+
// Cursor rules
|
|
1767
|
+
if (normalizedPath.includes('.cursor/rules/')) {
|
|
1768
|
+
return 'cursor_rule';
|
|
1769
|
+
}
|
|
1770
|
+
// Cursor extensions
|
|
1771
|
+
if (normalizedPath.includes('.vscode/extensions.json') ||
|
|
1772
|
+
normalizedPath.includes('state.vscdb')) {
|
|
1773
|
+
return 'cursor_extensions';
|
|
1774
|
+
}
|
|
1775
|
+
// Default: try to infer from path structure
|
|
1776
|
+
if (normalizedPath.includes('.claude') && normalizedPath.endsWith('.json')) {
|
|
1777
|
+
return 'claude_settings';
|
|
1778
|
+
}
|
|
1779
|
+
if (normalizedPath.includes('.cursor') && normalizedPath.endsWith('.json')) {
|
|
1780
|
+
return 'cursor_hooks';
|
|
1781
|
+
}
|
|
1782
|
+
return null;
|
|
1783
|
+
}
|
|
1784
|
+
/**
|
|
1785
|
+
* Log a single file by path
|
|
1786
|
+
*/
|
|
1787
|
+
async function logSingleFile(filePath) {
|
|
1788
|
+
const hardwareUuid = resolveHardwareUuid();
|
|
1789
|
+
// Ensure we have authentication
|
|
1790
|
+
const authKey = await ensureAuthentication(hardwareUuid);
|
|
1791
|
+
// Determine file type
|
|
1792
|
+
let fileType = determineFileTypeFromPath(filePath);
|
|
1793
|
+
// Read the file
|
|
1794
|
+
let rawContent = null;
|
|
1795
|
+
if (!existsSync(filePath)) {
|
|
1796
|
+
console.error(`File not found: ${filePath}`);
|
|
1797
|
+
return false;
|
|
1798
|
+
}
|
|
1799
|
+
// Special handling for SQLite databases (state.vscdb)
|
|
1800
|
+
// Check if this is the state.vscdb file (with or without fragment)
|
|
1801
|
+
const isVSCDB = filePath.includes('state.vscdb');
|
|
1802
|
+
if (isVSCDB) {
|
|
1803
|
+
// Extract the actual file path (remove fragment if present)
|
|
1804
|
+
const actualPath = filePath.split('#')[0];
|
|
1805
|
+
// Read the SQLite database using the provided path
|
|
1806
|
+
const vscdbState = readVSCDBState(actualPath);
|
|
1807
|
+
if (vscdbState) {
|
|
1808
|
+
// Check if there's a fragment (e.g., "#composerState" or "#chat.tools")
|
|
1809
|
+
const fragment = filePath.includes('#') ? filePath.split('#')[1] : null;
|
|
1810
|
+
if (fragment === 'composerState' && vscdbState.composerState) {
|
|
1811
|
+
rawContent = {
|
|
1812
|
+
composerState: vscdbState.composerState,
|
|
1813
|
+
source: 'state.vscdb',
|
|
1814
|
+
extracted_at: new Date().toISOString(),
|
|
1815
|
+
};
|
|
1816
|
+
fileType = 'vscode_settings';
|
|
1817
|
+
}
|
|
1818
|
+
else if (fragment === 'chat.tools' && vscdbState.chat) {
|
|
1819
|
+
rawContent = {
|
|
1820
|
+
chat: {
|
|
1821
|
+
tools: vscdbState.chat.tools,
|
|
1822
|
+
},
|
|
1823
|
+
source: 'state.vscdb',
|
|
1824
|
+
extracted_at: new Date().toISOString(),
|
|
1825
|
+
};
|
|
1826
|
+
fileType = 'vscode_settings';
|
|
1827
|
+
}
|
|
1828
|
+
else if (!fragment && vscdbState.composerState) {
|
|
1829
|
+
// No fragment specified, default to composerState
|
|
1830
|
+
rawContent = {
|
|
1831
|
+
composerState: vscdbState.composerState,
|
|
1832
|
+
source: 'state.vscdb',
|
|
1833
|
+
extracted_at: new Date().toISOString(),
|
|
1834
|
+
};
|
|
1835
|
+
fileType = 'vscode_settings';
|
|
1836
|
+
}
|
|
1837
|
+
else {
|
|
1838
|
+
console.error(`Could not extract ${fragment || 'data'} from state.vscdb`);
|
|
1839
|
+
return false;
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
else {
|
|
1843
|
+
console.error(`Could not read state.vscdb from ${actualPath}`);
|
|
1844
|
+
return false;
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
else {
|
|
1848
|
+
// Try reading as JSON first
|
|
1849
|
+
rawContent = readJSONFile(filePath);
|
|
1850
|
+
if (!rawContent) {
|
|
1851
|
+
// Try reading as markdown/text
|
|
1852
|
+
const markdownContent = readMarkdownFile(filePath);
|
|
1853
|
+
if (markdownContent !== null) {
|
|
1854
|
+
rawContent = {
|
|
1855
|
+
content: markdownContent,
|
|
1856
|
+
source: 'file',
|
|
1857
|
+
};
|
|
1858
|
+
// Infer file type from path if not determined
|
|
1859
|
+
if (!fileType) {
|
|
1860
|
+
if (filePath.includes('CLAUDE.md') || filePath.includes('claude')) {
|
|
1861
|
+
fileType = 'claude_rule_config';
|
|
1862
|
+
}
|
|
1863
|
+
else if (filePath.includes('.cursor')) {
|
|
1864
|
+
fileType = 'cursor_rule';
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
if (!rawContent) {
|
|
1871
|
+
console.error(`Could not read file: ${filePath}`);
|
|
1872
|
+
return false;
|
|
1873
|
+
}
|
|
1874
|
+
// If file type still not determined, default to claude_settings for JSON files
|
|
1875
|
+
if (!fileType) {
|
|
1876
|
+
if (filePath.includes('.claude') && filePath.endsWith('.json')) {
|
|
1877
|
+
fileType = 'claude_settings';
|
|
1878
|
+
}
|
|
1879
|
+
else if (filePath.includes('.cursor') && filePath.endsWith('.json')) {
|
|
1880
|
+
fileType = 'cursor_hooks';
|
|
1881
|
+
}
|
|
1882
|
+
else {
|
|
1883
|
+
console.error(`Could not determine file type for: ${filePath}`);
|
|
1884
|
+
return false;
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
const configFile = {
|
|
1888
|
+
file_type: fileType,
|
|
1889
|
+
file_path: filePath,
|
|
1890
|
+
raw_content: rawContent,
|
|
1891
|
+
};
|
|
1892
|
+
return await sendConfigFile(configFile, hardwareUuid, authKey);
|
|
1893
|
+
}
|
|
1207
1894
|
/**
|
|
1208
1895
|
* Main execution
|
|
1209
1896
|
*/
|
|
1210
1897
|
async function main() {
|
|
1898
|
+
// Log all files (original behavior)
|
|
1211
1899
|
const hardwareUuid = resolveHardwareUuid();
|
|
1212
1900
|
console.log(`Hardware UUID: ${hardwareUuid}`);
|
|
1213
1901
|
// Ensure we have authentication (do handshake if needed)
|
|
1214
1902
|
const authKey = await ensureAuthentication(hardwareUuid);
|
|
1215
1903
|
console.log('Authentication verified, proceeding with config file logging.');
|
|
1216
|
-
//
|
|
1217
|
-
const
|
|
1904
|
+
// Prefer API-driven collection: call endpoint to get the complete list of what to look for
|
|
1905
|
+
const endpointBase = loadEndpointBase();
|
|
1906
|
+
const patternsResponse = await getFileCollectionPatterns(endpointBase);
|
|
1907
|
+
const configFiles = patternsResponse?.patterns?.length
|
|
1908
|
+
? collectConfigFilesFromPatterns(patternsResponse.patterns, PROJECT_ROOT)
|
|
1909
|
+
: collectConfigFiles();
|
|
1218
1910
|
console.log(`Collected ${configFiles.length} configuration file(s).`);
|
|
1219
1911
|
if (configFiles.length === 0) {
|
|
1220
1912
|
console.log('No configuration files found to log.');
|
|
@@ -1239,4 +1931,4 @@ if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.includes
|
|
|
1239
1931
|
process.exit(1);
|
|
1240
1932
|
});
|
|
1241
1933
|
}
|
|
1242
|
-
export { main, collectConfigFiles, ensureAuthentication, sendConfigFile, createSignature, canonicalizePayload, readVSCDBState };
|
|
1934
|
+
export { main, collectConfigFiles, collectConfigFilesFromPatterns, resolvePatternToTargets, ensureAuthentication, sendConfigFile, createSignature, canonicalizePayload, readVSCDBState, logSingleFile, };
|