log-llm-config 1.0.26 → 1.0.30

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 (41) hide show
  1. package/dist/bootstrap_constants.js +5 -0
  2. package/dist/cli/bash_script_generator.js +83 -0
  3. package/dist/cli.js +54 -235
  4. package/dist/endpoint_client/http_transport.js +88 -0
  5. package/dist/endpoint_client/index.js +3 -0
  6. package/dist/endpoint_client/registry_api.js +37 -0
  7. package/dist/endpoint_client/startup_api.js +47 -0
  8. package/dist/endpoint_client/types.js +4 -0
  9. package/dist/list_mcp_tools.js +256 -0
  10. package/dist/log_config_files/auth/auth_flow.js +76 -0
  11. package/dist/log_config_files/auth/auth_key_store.js +29 -0
  12. package/dist/log_config_files/collection/config_collector.js +143 -0
  13. package/dist/log_config_files/collection/directory_collector.js +50 -0
  14. package/dist/log_config_files/collection/enrichment_helpers.js +53 -0
  15. package/dist/log_config_files/collection/file_type_rules.js +47 -0
  16. package/dist/log_config_files/collection/mcp_tool_collector.js +37 -0
  17. package/dist/log_config_files/collection/openclaw_helpers.js +55 -0
  18. package/dist/log_config_files/collection/plugin_collector.js +89 -0
  19. package/dist/log_config_files/collection/plugin_version_helpers.js +37 -0
  20. package/dist/log_config_files/index.js +19 -0
  21. package/dist/log_config_files/paths/path_constants_helpers.js +71 -0
  22. package/dist/log_config_files/paths/pattern_resolver.js +215 -0
  23. package/dist/log_config_files/readers/file_readers.js +69 -0
  24. package/dist/log_config_files/readers/vscdb_config_builder.js +111 -0
  25. package/dist/log_config_files/readers/vscdb_reader.js +117 -0
  26. package/dist/log_config_files/runtime/hardware_uuid.js +27 -0
  27. package/dist/log_config_files/runtime/hook_logger.js +51 -0
  28. package/dist/log_config_files/runtime/main_runner.js +174 -0
  29. package/dist/log_config_files/sender/batch_sender.js +176 -0
  30. package/dist/log_config_files/sender/endpoint_config.js +57 -0
  31. package/dist/log_config_files/sender/signing.js +27 -0
  32. package/dist/log_config_files.js +14 -2190
  33. package/dist/log_sensitive_paths_audit.js +4 -3
  34. package/dist/log_uuid/auth_key_store.js +71 -0
  35. package/dist/log_uuid/hardware_uuid.js +35 -0
  36. package/dist/log_uuid/index.js +11 -0
  37. package/dist/log_uuid/log_uuid_helper.js +30 -0
  38. package/dist/log_uuid/startup_sender.js +72 -0
  39. package/dist/log_uuid.js +11 -220
  40. package/dist/types/config_file_types.js +1 -0
  41. package/package.json +3 -3
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Bootstrap-level path constants — known before the API response is available.
3
+ * Used by auth, hook logging, and audit output. Do not add agent-specific paths here.
4
+ */
5
+ export const OPT_AI_SEC_MANAGEMENT_REL = 'opt-ai-sec/management';
@@ -0,0 +1,83 @@
1
+ const fileCategories = [
2
+ { label: 'Cursor: mcp.json', targets: ['./.cursor/mcp.json', '$HOME/.cursor/mcp.json'] },
3
+ { label: 'Claude: .mcp.json', targets: ['./mcp.json', './.mcp.json', '/Library/Application Support/ClaudeCode/managed-mcp.json'] },
4
+ { label: 'Claude: settings.json', targets: ['./.claude/settings.json', './.claude/settings.local.json', '$HOME/.claude/settings.json', '/Library/Application Support/ClaudeCode/managed-settings.json'] },
5
+ { label: 'Cursor: hooks.json', targets: ['./.cursor/hooks.json', '$HOME/.cursor/hooks.json', '/Library/Application Support/Cursor/hooks.json'] },
6
+ { label: 'Cursor: User/settings.json (user-level)', targets: ['$HOME/Library/Application Support/Cursor/User/settings.json'] },
7
+ ];
8
+ const sqliteCategories = [
9
+ {
10
+ label: 'Cursor: state.vscdb (composerState)',
11
+ dbPath: '$HOME/Library/Application Support/Cursor/User/globalStorage/state.vscdb',
12
+ table: 'ItemTable',
13
+ key: 'src.vs.platform.reactivestorage.browser.reactiveStorageServiceImpl.persistentStorage.applicationUser',
14
+ jsonPaths: [['composerState'], []],
15
+ },
16
+ ];
17
+ function buildFileCategoryLines(category) {
18
+ return [
19
+ `echo "===== ${category.label} ====="`,
20
+ 'files=(', ...category.targets.map((t) => ` "${t}"`), ')',
21
+ 'expanded_files=()', 'paths=()', 'display_paths=()',
22
+ 'for f in "${files[@]}"; do',
23
+ ' expanded=$(eval echo "$f")',
24
+ ' expanded_dir=$(dirname "$expanded")', ' expanded_base=$(basename "$expanded")',
25
+ ' abs_dir=$(cd "$expanded_dir" 2>/dev/null && pwd || echo "$expanded_dir")',
26
+ ' abs_path="$abs_dir/$expanded_base"', ' expanded_files+=("$abs_path")',
27
+ ' relative_path="$(to_relative "$abs_path")"', ' paths+=("$relative_path")', ' display_paths+=("$relative_path")',
28
+ 'done', 'echo ""',
29
+ 'if [ ${#paths[@]} -gt 0 ]; then', ' echo "Paths:"',
30
+ ' printf "%s\\n" "${paths[@]}" | awk \'!seen[$0]++\' | while IFS= read -r dir; do', ' echo "$dir"', ' done',
31
+ ' echo ""', 'else', ' echo "Paths: (none)"', 'fi', 'echo ""',
32
+ 'for idx in "${!expanded_files[@]}"; do', ' expanded="${expanded_files[$idx]}"', ' display="${display_paths[$idx]}"',
33
+ ' if [ -f "$expanded" ]; then',
34
+ ` echo "===== ${category.label}: $display ====="`, ' cat "$expanded"',
35
+ ' else', ` echo "===== ${category.label}: missing: $display ====="`, ' fi',
36
+ 'done', 'echo ""',
37
+ ];
38
+ }
39
+ function buildSqliteCategoryLines(category) {
40
+ const jsonPathsLiteral = JSON.stringify(category.jsonPaths);
41
+ return [
42
+ `echo "===== ${category.label} ====="`, `db_path="${category.dbPath}"`,
43
+ 'expanded=$(eval echo "$db_path")',
44
+ 'if [ -f "$expanded" ]; then', ' if command -v sqlite3 >/dev/null 2>&1; then',
45
+ ' echo "===== $expanded ====="',
46
+ ` query_result=$(sqlite3 "$expanded" "SELECT value FROM ${category.table} WHERE key='${category.key}'")`,
47
+ ' if [ -n "$query_result" ]; then',
48
+ ` echo "===== key: ${category.key} ====="`,
49
+ ` LOG_CONFIG_JSON_VALUE="$query_result" python3 - <<'PY'`,
50
+ 'import json', 'import os', '',
51
+ 'raw = os.environ.get("LOG_CONFIG_JSON_VALUE", "")',
52
+ 'if not raw:', ' print("{}")', ' raise SystemExit',
53
+ `paths = ${jsonPathsLiteral}`,
54
+ 'try:', ' data = json.loads(raw)',
55
+ 'except json.JSONDecodeError as exc:', ' print(f"failed to parse JSON: {exc}")', ' raise SystemExit',
56
+ 'def walk(obj, path):', ' cur = obj',
57
+ ' for segment in path:', ' if isinstance(cur, dict):', ' cur = cur.get(segment)', ' else:', ' return None',
58
+ ' return cur',
59
+ 'selected_value = None', 'selected_label = None',
60
+ 'for path in paths:', ' value = walk(data, path)',
61
+ ' if value is not None:', ' selected_value = value', ' selected_label = ".".join(path) if path else "root"', ' break',
62
+ 'if selected_value is None:', ' print("{}")',
63
+ 'else:', ' print(f"[path: {selected_label}]")', ' print(json.dumps(selected_value, indent=2, sort_keys=True))',
64
+ 'PY',
65
+ ' else', ` echo "===== key not found: ${category.key} ====="`, ' fi',
66
+ ' else', ' echo "===== sqlite3 not found; skipping ====="', ' fi',
67
+ 'else', ' echo "===== missing: $expanded ====="', 'fi', 'echo ""',
68
+ ];
69
+ }
70
+ function renderBashScript() {
71
+ const scriptLines = [
72
+ '#!/usr/bin/env bash', 'set -euo pipefail',
73
+ 'output_file="$(pwd)/configs.txt"', ': > "$output_file"', 'exec >"$output_file"', 'exec 2>&1', '',
74
+ 'repo_root="$(pwd)"',
75
+ 'to_relative() {', ' local path="$1"', ' if [[ "$path" == "$repo_root"* ]]; then',
76
+ ' local suffix="${path#"$repo_root"}"', ' if [ -z "$suffix" ]; then', ' echo "<project_root>"',
77
+ ' else', ' echo "<project_root>${suffix}"', ' fi', ' else', ' echo "$path"', ' fi', '}', '',
78
+ ...fileCategories.flatMap(buildFileCategoryLines),
79
+ ...sqliteCategories.flatMap(buildSqliteCategoryLines),
80
+ ];
81
+ return scriptLines.join('\n');
82
+ }
83
+ export { renderBashScript };
package/dist/cli.js CHANGED
@@ -1,76 +1,25 @@
1
1
  #!/usr/bin/env node
2
+ import { renderBashScript } from './cli/bash_script_generator.js';
2
3
  const args = process.argv.slice(2);
3
4
  const main = async () => {
4
5
  if (args[0] === 'log_uuid') {
5
- // Allow passing:
6
- // - UUID: positional or `--uuid <hardware_uuid>` / `-u <hardware_uuid>`
7
- // - Git/user context: `--user`, `--repo`, `--branch`, `--agent`
8
- let uuidArg;
9
- let userArg;
10
- let repoArg;
11
- let branchArg;
12
- let agentArg;
13
- for (let i = 1; i < args.length; i++) {
14
- const arg = args[i];
15
- if (arg === '--uuid' || arg === '-u') {
16
- uuidArg = args[i + 1];
17
- i++;
18
- continue;
19
- }
20
- if (arg === '--user') {
21
- userArg = args[i + 1];
22
- i++;
23
- continue;
24
- }
25
- if (arg === '--repo') {
26
- repoArg = args[i + 1];
27
- i++;
28
- continue;
29
- }
30
- if (arg === '--branch') {
31
- branchArg = args[i + 1];
32
- i++;
33
- continue;
34
- }
35
- if (arg === '--agent') {
36
- agentArg = args[i + 1];
37
- i++;
38
- continue;
39
- }
40
- // First non-flag arg as UUID if not already set
41
- if (!arg.startsWith('-') && !uuidArg) {
42
- uuidArg = arg;
43
- }
44
- }
45
- if (uuidArg) {
46
- process.env.OPTIMUS_HARDWARE_UUID = uuidArg;
47
- }
48
- if (userArg) {
49
- process.env.GITHUB_USER = userArg;
50
- }
51
- if (repoArg) {
52
- process.env.GITHUB_REPOSITORY = repoArg;
53
- }
54
- if (branchArg) {
55
- process.env.GITHUB_REF_NAME = branchArg;
56
- }
57
- if (agentArg) {
58
- process.env.OPTIMUS_AGENT = agentArg;
59
- }
60
- await import('./log_uuid.js');
6
+ parseAndSetLogUuidEnvVars();
7
+ await import('./log_uuid/index.js');
61
8
  return;
62
9
  }
63
10
  if (args[0] === 'log_config_files') {
64
- const { main, logSingleFile } = await import('./log_config_files.js');
65
- // Check if a file path was provided
11
+ const { main, logSingleFile } = await import('./log_config_files/index.js');
66
12
  const filePathArg = args.find((arg, idx) => idx > 0 && !arg.startsWith('-') && (arg.includes('/') || arg.endsWith('.json') || arg.endsWith('.md')));
67
13
  if (filePathArg) {
68
- // Log single file
69
14
  const success = await logSingleFile(filePathArg);
70
- process.exit(success ? 0 : 1);
15
+ try {
16
+ process.exit(success ? 0 : 1);
17
+ }
18
+ catch {
19
+ // process.exit may be mocked in tests (throws instead of exiting); allow process to continue
20
+ }
71
21
  return;
72
22
  }
73
- // Otherwise, log all files
74
23
  await main();
75
24
  return;
76
25
  }
@@ -79,179 +28,49 @@ const main = async () => {
79
28
  await main();
80
29
  return;
81
30
  }
82
- // If no command matched, output bash script (legacy behavior)
83
- // This should only happen when running the main command without subcommands
84
- if (args.length === 0 || !['log_uuid', 'log_config_files', 'log_sensitive_paths_audit'].includes(args[0])) {
85
- const fileCategories = [
86
- {
87
- label: 'Cursor: mcp.json',
88
- targets: ['./.cursor/mcp.json', '$HOME/.cursor/mcp.json']
89
- },
90
- {
91
- label: 'Claude: .mcp.json',
92
- targets: ['./mcp.json', './.mcp.json', '/Library/Application Support/ClaudeCode/managed-mcp.json']
93
- },
94
- {
95
- label: 'Claude: settings.json',
96
- targets: [
97
- './.claude/settings.json',
98
- './.claude/settings.local.json',
99
- '$HOME/.claude/settings.json',
100
- '/Library/Application Support/ClaudeCode/managed-settings.json'
101
- ]
102
- },
103
- {
104
- label: 'Cursor: hooks.json',
105
- targets: [
106
- './.cursor/hooks.json',
107
- '$HOME/.cursor/hooks.json',
108
- '/Library/Application Support/Cursor/hooks.json'
109
- ]
110
- },
111
- {
112
- label: 'Cursor: User/settings.json (user-level)',
113
- targets: ['$HOME/Library/Application Support/Cursor/User/settings.json']
114
- }
115
- ];
116
- const sqliteCategories = [
117
- {
118
- label: 'Cursor: state.vscdb (composerState)',
119
- dbPath: '$HOME/Library/Application Support/Cursor/User/globalStorage/state.vscdb',
120
- table: 'ItemTable',
121
- key: 'src.vs.platform.reactivestorage.browser.reactiveStorageServiceImpl.persistentStorage.applicationUser',
122
- jsonPaths: [['composerState'], []]
123
- }
124
- ];
125
- const buildFileCategoryLines = (category) => [
126
- `echo "===== ${category.label} ====="`,
127
- 'files=(',
128
- ...category.targets.map((target) => ` "${target}"`),
129
- ')',
130
- 'expanded_files=()',
131
- 'paths=()',
132
- 'display_paths=()',
133
- 'for f in "${files[@]}"; do',
134
- ' expanded=$(eval echo "$f")',
135
- ' expanded_dir=$(dirname "$expanded")',
136
- ' expanded_base=$(basename "$expanded")',
137
- ' abs_dir=$(cd "$expanded_dir" 2>/dev/null && pwd || echo "$expanded_dir")',
138
- ' abs_path="$abs_dir/$expanded_base"',
139
- ' expanded_files+=("$abs_path")',
140
- ' relative_path="$(to_relative "$abs_path")"',
141
- ' paths+=("$relative_path")',
142
- ' display_paths+=("$relative_path")',
143
- 'done',
144
- 'echo ""',
145
- 'if [ ${#paths[@]} -gt 0 ]; then',
146
- ' echo "Paths:"',
147
- ' printf "%s\\n" "${paths[@]}" | awk \'!seen[$0]++\' | while IFS= read -r dir; do',
148
- ' echo "$dir"',
149
- ' done',
150
- ' echo ""',
151
- 'else',
152
- ' echo "Paths: (none)"',
153
- 'fi',
154
- 'echo ""',
155
- 'for idx in "${!expanded_files[@]}"; do',
156
- ' expanded="${expanded_files[$idx]}"',
157
- ' display="${display_paths[$idx]}"',
158
- ' if [ -f "$expanded" ]; then',
159
- ` echo "===== ${category.label}: $display ====="`,
160
- ' cat "$expanded"',
161
- ' else',
162
- ` echo "===== ${category.label}: missing: $display ====="`,
163
- ' fi',
164
- 'done',
165
- 'echo ""'
166
- ];
167
- const buildSqliteCategoryLines = (category) => {
168
- const jsonPathsLiteral = JSON.stringify(category.jsonPaths);
169
- return [
170
- `echo "===== ${category.label} ====="`,
171
- `db_path="${category.dbPath}"`,
172
- 'expanded=$(eval echo "$db_path")',
173
- 'if [ -f "$expanded" ]; then',
174
- ' if command -v sqlite3 >/dev/null 2>&1; then',
175
- ' echo "===== $expanded ====="',
176
- ` query_result=$(sqlite3 "$expanded" "SELECT value FROM ${category.table} WHERE key='${category.key}'")`,
177
- ' if [ -n "$query_result" ]; then',
178
- ` echo "===== key: ${category.key} ====="`,
179
- ` LOG_CONFIG_JSON_VALUE="$query_result" python3 - <<'PY'`,
180
- 'import json',
181
- 'import os',
182
- '',
183
- 'raw = os.environ.get("LOG_CONFIG_JSON_VALUE", "")',
184
- 'if not raw:',
185
- ' print("{}")',
186
- ' raise SystemExit',
187
- `paths = ${jsonPathsLiteral}`,
188
- 'try:',
189
- ' data = json.loads(raw)',
190
- 'except json.JSONDecodeError as exc:',
191
- ' print(f"failed to parse JSON: {exc}")',
192
- ' raise SystemExit',
193
- 'def walk(obj, path):',
194
- ' cur = obj',
195
- ' for segment in path:',
196
- ' if isinstance(cur, dict):',
197
- ' cur = cur.get(segment)',
198
- ' else:',
199
- ' return None',
200
- ' return cur',
201
- 'selected_value = None',
202
- 'selected_label = None',
203
- 'for path in paths:',
204
- ' value = walk(data, path)',
205
- ' if value is not None:',
206
- ' selected_value = value',
207
- ' selected_label = ".".join(path) if path else "root"',
208
- ' break',
209
- 'if selected_value is None:',
210
- ' print("{}")',
211
- 'else:',
212
- ' print(f"[path: {selected_label}]")',
213
- ' print(json.dumps(selected_value, indent=2, sort_keys=True))',
214
- 'PY',
215
- ' else',
216
- ` echo "===== key not found: ${category.key} ====="`,
217
- ' fi',
218
- ' else',
219
- ' echo "===== sqlite3 not found; skipping ====="',
220
- ' fi',
221
- 'else',
222
- ' echo "===== missing: $expanded ====="',
223
- 'fi',
224
- 'echo ""'
225
- ];
226
- };
227
- const scriptLines = [
228
- '#!/usr/bin/env bash',
229
- 'set -euo pipefail',
230
- 'output_file="$(pwd)/configs.txt"',
231
- ': > "$output_file"',
232
- 'exec >"$output_file"',
233
- 'exec 2>&1',
234
- '',
235
- 'repo_root="$(pwd)"',
236
- 'to_relative() {',
237
- ' local path="$1"',
238
- ' if [[ "$path" == "$repo_root"* ]]; then',
239
- ' local suffix="${path#"$repo_root"}"',
240
- ' if [ -z "$suffix" ]; then',
241
- ' echo "<project_root>"',
242
- ' else',
243
- ' echo "<project_root>${suffix}"',
244
- ' fi',
245
- ' else',
246
- ' echo "$path"',
247
- ' fi',
248
- '}',
249
- '',
250
- ...fileCategories.flatMap(buildFileCategoryLines),
251
- ...sqliteCategories.flatMap(buildSqliteCategoryLines)
252
- ];
253
- console.log(scriptLines.join('\n'));
254
- }
31
+ // No command matched output legacy bash script
32
+ console.log(renderBashScript());
255
33
  };
34
+ function parseAndSetLogUuidEnvVars() {
35
+ let uuidArg;
36
+ let userArg;
37
+ let repoArg;
38
+ let branchArg;
39
+ let agentArg;
40
+ for (let i = 1; i < args.length; i++) {
41
+ const arg = args[i];
42
+ if (arg === '--uuid' || arg === '-u') {
43
+ uuidArg = args[++i];
44
+ continue;
45
+ }
46
+ if (arg === '--user') {
47
+ userArg = args[++i];
48
+ continue;
49
+ }
50
+ if (arg === '--repo') {
51
+ repoArg = args[++i];
52
+ continue;
53
+ }
54
+ if (arg === '--branch') {
55
+ branchArg = args[++i];
56
+ continue;
57
+ }
58
+ if (arg === '--agent') {
59
+ agentArg = args[++i];
60
+ continue;
61
+ }
62
+ if (!arg.startsWith('-') && !uuidArg)
63
+ uuidArg = arg;
64
+ }
65
+ if (uuidArg)
66
+ process.env.OPTIMUS_HARDWARE_UUID = uuidArg;
67
+ if (userArg)
68
+ process.env.GITHUB_USER = userArg;
69
+ if (repoArg)
70
+ process.env.GITHUB_REPOSITORY = repoArg;
71
+ if (branchArg)
72
+ process.env.GITHUB_REF_NAME = branchArg;
73
+ if (agentArg)
74
+ process.env.OPTIMUS_AGENT = agentArg;
75
+ }
256
76
  void main();
257
- export {};
@@ -0,0 +1,88 @@
1
+ import http from 'node:http';
2
+ import https from 'node:https';
3
+ import { URL } from 'node:url';
4
+ /** Normalize localhost/::1 to 127.0.0.1 to avoid IPv6 resolution issues. */
5
+ function resolveHostname(raw) {
6
+ return raw === 'localhost' || raw === '::1' ? '127.0.0.1' : raw;
7
+ }
8
+ function buildGetOptions(url, timeoutMs) {
9
+ const isHttps = url.protocol === 'https:';
10
+ return {
11
+ hostname: resolveHostname(url.hostname),
12
+ port: url.port || (isHttps ? 443 : 80),
13
+ path: url.pathname + url.search,
14
+ method: 'GET',
15
+ timeout: timeoutMs,
16
+ };
17
+ }
18
+ function buildBodyOptions(url, payload, method, timeoutMs) {
19
+ const isHttps = url.protocol === 'https:';
20
+ return {
21
+ hostname: resolveHostname(url.hostname),
22
+ port: url.port || (isHttps ? 443 : 80),
23
+ path: `${url.pathname}${url.search}`,
24
+ method,
25
+ headers: {
26
+ 'Content-Type': 'application/json',
27
+ 'Content-Length': Buffer.byteLength(payload).toString(),
28
+ },
29
+ timeout: timeoutMs,
30
+ };
31
+ }
32
+ /** Execute a GET request. Returns statusCode=0 and empty body on network/timeout failure. */
33
+ export function executeGet(urlStr, timeoutMs) {
34
+ const url = new URL(urlStr);
35
+ const options = buildGetOptions(url, timeoutMs);
36
+ const transport = url.protocol === 'https:' ? https.request : http.request;
37
+ return new Promise((resolve) => {
38
+ const req = transport(options, (res) => {
39
+ let body = '';
40
+ res.setEncoding('utf8');
41
+ res.on('data', (chunk) => { body += chunk; });
42
+ res.on('end', () => resolve({ statusCode: res.statusCode ?? 0, body }));
43
+ });
44
+ req.on('error', () => resolve({ statusCode: 0, body: '' }));
45
+ req.on('timeout', () => { req.destroy(); resolve({ statusCode: 0, body: '' }); });
46
+ req.end();
47
+ });
48
+ }
49
+ /** Execute a POST/PATCH request. Rejects on network error or timeout. */
50
+ export function executeBody(urlStr, method, payload, timeoutMs) {
51
+ const url = new URL(urlStr);
52
+ const options = buildBodyOptions(url, payload, method, timeoutMs);
53
+ const transport = url.protocol === 'https:' ? https.request : http.request;
54
+ return new Promise((resolve, reject) => {
55
+ let done = false;
56
+ const timer = setTimeout(() => {
57
+ if (!done) {
58
+ done = true;
59
+ req.destroy();
60
+ reject(new Error(`Request timeout after ${timeoutMs}ms`));
61
+ }
62
+ }, timeoutMs);
63
+ const req = transport(options, (res) => {
64
+ let body = '';
65
+ res.setEncoding('utf8');
66
+ res.on('data', (chunk) => { body += chunk; });
67
+ res.on('end', () => {
68
+ done = true;
69
+ clearTimeout(timer);
70
+ resolve({ statusCode: res.statusCode ?? 0, statusMessage: res.statusMessage ?? '', headers: res.headers, body });
71
+ });
72
+ });
73
+ req.on('error', (err) => {
74
+ done = true;
75
+ clearTimeout(timer);
76
+ console.error('Request error:', err.message, err.code);
77
+ reject(err);
78
+ });
79
+ req.on('timeout', () => {
80
+ done = true;
81
+ clearTimeout(timer);
82
+ req.destroy();
83
+ reject(new Error(`Request timeout after ${timeoutMs}ms`));
84
+ });
85
+ req.write(payload);
86
+ req.end();
87
+ });
88
+ }
@@ -0,0 +1,3 @@
1
+ export { FILE_PATH_REGISTRY_FILE_PATTERNS_PATH, FILE_PATH_REGISTRY_SENSITIVE_PATHS_PATH, } from './types.js';
2
+ export { getFileCollectionPatterns, getSensitivePathsAuditCandidates } from './registry_api.js';
3
+ export { postStartupPayload, patchPayload, classifyEndpointResponse } from './startup_api.js';
@@ -0,0 +1,37 @@
1
+ import { executeGet } from './http_transport.js';
2
+ import { FILE_PATH_REGISTRY_FILE_PATTERNS_PATH, FILE_PATH_REGISTRY_SENSITIVE_PATHS_PATH, } from './types.js';
3
+ function buildApiUrl(base, path) {
4
+ return `${base.replace(/\/+$/, '')}${path}`.replace(/([^/])\/+/g, '$1/');
5
+ }
6
+ /**
7
+ * GET file collection patterns from the backend (what to look for).
8
+ * No auth required. Returns the complete list of path + type + file_type.
9
+ */
10
+ export const getFileCollectionPatterns = async (apiBaseUrl, timeoutMs = 5000) => {
11
+ const { statusCode, body } = await executeGet(buildApiUrl(apiBaseUrl, FILE_PATH_REGISTRY_FILE_PATTERNS_PATH), timeoutMs);
12
+ if (statusCode !== 200 || !body)
13
+ return null;
14
+ try {
15
+ const parsed = JSON.parse(body);
16
+ return Array.isArray(parsed.patterns) && typeof parsed.count === 'number' ? parsed : null;
17
+ }
18
+ catch {
19
+ return null;
20
+ }
21
+ };
22
+ /**
23
+ * GET sensitive path audit candidates from the backend (concrete paths to check for existence).
24
+ * No auth required. Returns path templates (~ = home). Client checks existence only, never sends contents.
25
+ */
26
+ export const getSensitivePathsAuditCandidates = async (apiBaseUrl, timeoutMs = 5000) => {
27
+ const { statusCode, body } = await executeGet(buildApiUrl(apiBaseUrl, FILE_PATH_REGISTRY_SENSITIVE_PATHS_PATH), timeoutMs);
28
+ if (statusCode !== 200 || !body)
29
+ return null;
30
+ try {
31
+ const parsed = JSON.parse(body);
32
+ return Array.isArray(parsed.paths) ? parsed : null;
33
+ }
34
+ catch {
35
+ return null;
36
+ }
37
+ };
@@ -0,0 +1,47 @@
1
+ import { executeBody } from './http_transport.js';
2
+ export const postStartupPayload = async (endpointUrl, body, timeoutMs = 5000) => {
3
+ const payload = JSON.stringify(body);
4
+ console.log('Sending payload to endpoint:', endpointUrl, payload);
5
+ const { statusCode, statusMessage, headers, body: responseBody } = await executeBody(endpointUrl, 'POST', payload, timeoutMs);
6
+ console.log(`HTTP ${statusCode} ${statusMessage}`);
7
+ console.log('Response headers:', headers);
8
+ if (statusCode >= 400) {
9
+ const msg = statusCode === 413
10
+ ? `413 Request Entity Too Large: ${responseBody.substring(0, 200)}`
11
+ : `HTTP ${statusCode} ${statusMessage}: ${responseBody.substring(0, 200)}`;
12
+ throw new Error(msg);
13
+ }
14
+ if (!responseBody)
15
+ return { status: 'error', message: 'Empty response from endpoint' };
16
+ console.log('Raw endpoint response body:', responseBody);
17
+ try {
18
+ return JSON.parse(responseBody);
19
+ }
20
+ catch (error) {
21
+ throw new Error(`Failed to parse endpoint response (${error.message}): ${responseBody}`);
22
+ }
23
+ };
24
+ export const patchPayload = async (endpointUrl, body, timeoutMs = 10000) => {
25
+ const payload = JSON.stringify(body);
26
+ const { body: responseBody } = await executeBody(endpointUrl, 'PATCH', payload, timeoutMs);
27
+ if (!responseBody)
28
+ return { status: 'error', error: 'empty_response' };
29
+ try {
30
+ return JSON.parse(responseBody);
31
+ }
32
+ catch {
33
+ throw new Error(`Invalid JSON: ${responseBody.slice(0, 200)}`);
34
+ }
35
+ };
36
+ export const classifyEndpointResponse = (response) => {
37
+ if (response.status === 'key_issued' && response.key) {
38
+ return { branch: 'key_issued', message: 'Server issued a new symmetric key; store it for future signatures.', key: response.key, keyId: response.key_id };
39
+ }
40
+ if (response.status === 'accepted' && response.signature_valid) {
41
+ return { branch: 'accepted', message: 'Startup payload accepted and signature verified.' };
42
+ }
43
+ if (response.status === 'error' && response.error) {
44
+ return { branch: 'error', message: response.error };
45
+ }
46
+ return { branch: 'error', message: response.message || 'Endpoint returned an unexpected response.' };
47
+ };
@@ -0,0 +1,4 @@
1
+ /** Path suffix for file collection patterns (backend: api/file-path-registry/file-patterns/). */
2
+ export const FILE_PATH_REGISTRY_FILE_PATTERNS_PATH = '/api/file-path-registry/file-patterns/';
3
+ /** Path suffix for sensitive paths audit candidates (backend: api/file-path-registry/sensitive-paths-audit-candidates/). */
4
+ export const FILE_PATH_REGISTRY_SENSITIVE_PATHS_PATH = '/api/file-path-registry/sensitive-paths-audit-candidates/';