@optimuslabs/harness-map-staging 1.5.16

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 (88) hide show
  1. package/README.md +46 -0
  2. package/dist/apply_deferred_vscdb.js +38 -0
  3. package/dist/bootstrap_constants.js +15 -0
  4. package/dist/cli/bash_script_generator.js +95 -0
  5. package/dist/cli.js +144 -0
  6. package/dist/cli_invocation_match.js +30 -0
  7. package/dist/compliance_check_runner.js +57 -0
  8. package/dist/compliance_prompt_gate.js +396 -0
  9. package/dist/dialog_prefs_cli.js +109 -0
  10. package/dist/endpoint_client/compress_payload.js +25 -0
  11. package/dist/endpoint_client/http_transport.js +90 -0
  12. package/dist/endpoint_client/index.js +3 -0
  13. package/dist/endpoint_client/registry_api.js +41 -0
  14. package/dist/endpoint_client/startup_api.js +44 -0
  15. package/dist/endpoint_client/types.js +4 -0
  16. package/dist/execute_trusted_restarts.js +60 -0
  17. package/dist/log_config_files/auth/auth_flow.js +22 -0
  18. package/dist/log_config_files/auth/auth_key_store.js +14 -0
  19. package/dist/log_config_files/collection/claude_desktop_extensions_collector.js +248 -0
  20. package/dist/log_config_files/collection/claude_known_projects_collector.js +42 -0
  21. package/dist/log_config_files/collection/claude_token_usage_collector.js +203 -0
  22. package/dist/log_config_files/collection/codex_token_usage_collector.js +342 -0
  23. package/dist/log_config_files/collection/config_collector.js +245 -0
  24. package/dist/log_config_files/collection/copilot_token_usage_collector.js +167 -0
  25. package/dist/log_config_files/collection/cowork_desktop_version_collector.js +60 -0
  26. package/dist/log_config_files/collection/cowork_session_whitelist.js +89 -0
  27. package/dist/log_config_files/collection/cursor_project_mcp_collector.js +230 -0
  28. package/dist/log_config_files/collection/cursor_project_workspace_activity.js +73 -0
  29. package/dist/log_config_files/collection/cursor_token_usage_collector.js +192 -0
  30. package/dist/log_config_files/collection/directory_collector.js +126 -0
  31. package/dist/log_config_files/collection/enrichment_helpers.js +53 -0
  32. package/dist/log_config_files/collection/ensure_cursor_user_settings_snapshot.js +50 -0
  33. package/dist/log_config_files/collection/file_type_rules.js +47 -0
  34. package/dist/log_config_files/collection/grok_cli_version_collector.js +53 -0
  35. package/dist/log_config_files/collection/hermes_token_usage_collector.js +241 -0
  36. package/dist/log_config_files/collection/mcp_tool_collector.js +106 -0
  37. package/dist/log_config_files/collection/metadata_merge.js +43 -0
  38. package/dist/log_config_files/collection/openclaw_helpers.js +55 -0
  39. package/dist/log_config_files/collection/openclaw_token_usage_collector.js +223 -0
  40. package/dist/log_config_files/collection/opencode_token_usage_collector.js +177 -0
  41. package/dist/log_config_files/collection/pi_token_usage_collector.js +239 -0
  42. package/dist/log_config_files/collection/plugin_collector.js +223 -0
  43. package/dist/log_config_files/collection/plugin_version_helpers.js +37 -0
  44. package/dist/log_config_files/collection/skills_cli_collector.js +162 -0
  45. package/dist/log_config_files/index.js +19 -0
  46. package/dist/log_config_files/paths/path_constants_helpers.js +71 -0
  47. package/dist/log_config_files/paths/pattern_resolver.js +326 -0
  48. package/dist/log_config_files/readers/composer_shadow_merge.js +67 -0
  49. package/dist/log_config_files/readers/cursor_shadow_merge_ruleset.js +46 -0
  50. package/dist/log_config_files/readers/file_readers.js +196 -0
  51. package/dist/log_config_files/readers/vscdb_config_builder.js +148 -0
  52. package/dist/log_config_files/readers/vscdb_reactive_storage.js +62 -0
  53. package/dist/log_config_files/readers/vscdb_reader.js +337 -0
  54. package/dist/log_config_files/runtime/client_event_reporter.js +347 -0
  55. package/dist/log_config_files/runtime/compliance_check.js +1013 -0
  56. package/dist/log_config_files/runtime/compliance_session_log.js +378 -0
  57. package/dist/log_config_files/runtime/dialog_preferences.js +31 -0
  58. package/dist/log_config_files/runtime/hardware_uuid.js +36 -0
  59. package/dist/log_config_files/runtime/hook_logger.js +267 -0
  60. package/dist/log_config_files/runtime/hook_type_for_request.js +30 -0
  61. package/dist/log_config_files/runtime/log_metadata.js +122 -0
  62. package/dist/log_config_files/runtime/main_runner.js +376 -0
  63. package/dist/log_config_files/runtime/management_storage.js +144 -0
  64. package/dist/log_config_files/runtime/ops_target_path.js +18 -0
  65. package/dist/log_config_files/runtime/remediation_apply_tracking.js +152 -0
  66. package/dist/log_config_files/runtime/remediation_config_path.js +140 -0
  67. package/dist/log_config_files/runtime/remediation_sync.js +1705 -0
  68. package/dist/log_config_files/runtime/secret_regex_scan.js +126 -0
  69. package/dist/log_config_files/runtime/sqlite_binary.js +124 -0
  70. package/dist/log_config_files/runtime/trusted_restarts.js +113 -0
  71. package/dist/log_config_files/runtime/workspace_repo.js +170 -0
  72. package/dist/log_config_files/runtime/worktree_absent.js +50 -0
  73. package/dist/log_config_files/runtime/worktree_scanner.js +137 -0
  74. package/dist/log_config_files/sender/batch_sender.js +283 -0
  75. package/dist/log_config_files/sender/endpoint_config.js +24 -0
  76. package/dist/log_config_files/sender/signing.js +1 -0
  77. package/dist/log_sensitive_paths_audit.js +89 -0
  78. package/dist/log_uuid/auth_key_store.js +37 -0
  79. package/dist/log_uuid/hardware_uuid.js +35 -0
  80. package/dist/log_uuid/index.js +11 -0
  81. package/dist/log_uuid/log_uuid_helper.js +30 -0
  82. package/dist/log_uuid/startup_sender.js +131 -0
  83. package/dist/log_uuid/user_profile.js +192 -0
  84. package/dist/remediation_change_preview.js +182 -0
  85. package/dist/tofu.js +22 -0
  86. package/dist/tofu_environment.js +36 -0
  87. package/dist/types/config_file_types.js +1 -0
  88. package/package.json +67 -0
@@ -0,0 +1,137 @@
1
+ import { existsSync, readdirSync, statSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ /** Worktrees touched within this window are reported and collected (Phase 1). */
4
+ export const WORKTREE_ACTIVE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
5
+ const IN_REPO_SEGMENT = '/.claude/worktrees/';
6
+ const HOME_SEGMENT = '/.claude-worktrees/';
7
+ export function normalizePathForWorktree(path) {
8
+ return path.replace(/\\/g, '/');
9
+ }
10
+ export function parseWorktreeRootFromPath(filePath) {
11
+ const norm = normalizePathForWorktree(filePath);
12
+ if (norm.includes(HOME_SEGMENT)) {
13
+ const parts = norm.split(HOME_SEGMENT);
14
+ if (parts.length !== 2)
15
+ return null;
16
+ const prefix = parts[0];
17
+ const after = parts[1].replace(/^\/+/, '');
18
+ const segments = after.split('/');
19
+ if (segments.length < 2)
20
+ return null;
21
+ return `${prefix}${HOME_SEGMENT}${segments[0]}/${segments[1]}`.replace(/\/+$/, '');
22
+ }
23
+ if (norm.includes(IN_REPO_SEGMENT)) {
24
+ const idx = norm.indexOf(IN_REPO_SEGMENT);
25
+ const before = norm.slice(0, idx).replace(/\/+$/, '');
26
+ const after = norm.slice(idx + IN_REPO_SEGMENT.length).replace(/^\/+/, '');
27
+ const name = after.split('/')[0];
28
+ if (!name)
29
+ return null;
30
+ return `${before}${IN_REPO_SEGMENT}${name}`.replace(/\/+$/, '');
31
+ }
32
+ return null;
33
+ }
34
+ function isActiveDir(dirPath, maxAgeMs) {
35
+ if (!existsSync(dirPath))
36
+ return false;
37
+ try {
38
+ const st = statSync(dirPath);
39
+ if (!st.isDirectory())
40
+ return false;
41
+ return Date.now() - st.mtimeMs <= maxAgeMs;
42
+ }
43
+ catch {
44
+ return false;
45
+ }
46
+ }
47
+ function scanInRepoWorktrees(projectRoot, maxAgeMs) {
48
+ const base = join(projectRoot, '.claude', 'worktrees');
49
+ if (!existsSync(base))
50
+ return [];
51
+ const repoName = projectRoot.split(/[/\\]/).filter(Boolean).pop() || '';
52
+ const entries = [];
53
+ try {
54
+ for (const dirent of readdirSync(base, { withFileTypes: true })) {
55
+ if (!dirent.isDirectory())
56
+ continue;
57
+ const root = join(base, dirent.name);
58
+ if (!isActiveDir(root, maxAgeMs))
59
+ continue;
60
+ const st = statSync(root);
61
+ entries.push({
62
+ worktree_root: normalizePathForWorktree(root),
63
+ worktree_name: dirent.name,
64
+ repo_identifier: repoName,
65
+ layout: 'in_repo',
66
+ last_modified: st.mtime.toISOString(),
67
+ });
68
+ }
69
+ }
70
+ catch {
71
+ return entries;
72
+ }
73
+ return entries;
74
+ }
75
+ function scanHomeWorktrees(home, maxAgeMs) {
76
+ const base = join(home, '.claude-worktrees');
77
+ if (!existsSync(base))
78
+ return [];
79
+ const entries = [];
80
+ try {
81
+ for (const repoDir of readdirSync(base, { withFileTypes: true })) {
82
+ if (!repoDir.isDirectory())
83
+ continue;
84
+ const repoPath = join(base, repoDir.name);
85
+ let worktreeNames = [];
86
+ try {
87
+ worktreeNames = readdirSync(repoPath, { withFileTypes: true })
88
+ .filter((d) => d.isDirectory())
89
+ .map((d) => d.name);
90
+ }
91
+ catch {
92
+ continue;
93
+ }
94
+ for (const wtName of worktreeNames) {
95
+ const root = join(repoPath, wtName);
96
+ if (!isActiveDir(root, maxAgeMs))
97
+ continue;
98
+ const st = statSync(root);
99
+ entries.push({
100
+ worktree_root: normalizePathForWorktree(root),
101
+ worktree_name: wtName,
102
+ repo_identifier: repoDir.name,
103
+ layout: 'home',
104
+ last_modified: st.mtime.toISOString(),
105
+ });
106
+ }
107
+ }
108
+ }
109
+ catch {
110
+ return entries;
111
+ }
112
+ return entries;
113
+ }
114
+ /**
115
+ * Scan project and home Claude worktree directories; return those active within maxAgeMs.
116
+ */
117
+ export function scanActiveWorktrees(projectRoot, home, maxAgeMs = WORKTREE_ACTIVE_MAX_AGE_MS) {
118
+ const byRoot = new Map();
119
+ for (const e of [...scanInRepoWorktrees(projectRoot, maxAgeMs), ...scanHomeWorktrees(home, maxAgeMs)]) {
120
+ byRoot.set(e.worktree_root, e);
121
+ }
122
+ return [...byRoot.values()].sort((a, b) => a.worktree_root.localeCompare(b.worktree_root));
123
+ }
124
+ export function activeWorktreeRootSet(entries) {
125
+ return new Set(entries.map((e) => e.worktree_root));
126
+ }
127
+ export function filterConfigFilesToActiveWorktrees(files, activeRoots) {
128
+ if (activeRoots.size === 0) {
129
+ return files.filter((f) => parseWorktreeRootFromPath(f.file_path) === null);
130
+ }
131
+ return files.filter((f) => {
132
+ const root = parseWorktreeRootFromPath(f.file_path);
133
+ if (root === null)
134
+ return true;
135
+ return activeRoots.has(root);
136
+ });
137
+ }
@@ -0,0 +1,283 @@
1
+ import { postStartupPayload, patchPayload } from '../../endpoint_client/index.js';
2
+ import { createSignature } from './signing.js';
3
+ import { loadEndpointBase } from './endpoint_config.js';
4
+ import { hookRunLog } from '../runtime/hook_logger.js';
5
+ import { canonicalCursorUserStateVscdbPath } from '../runtime/remediation_config_path.js';
6
+ import { resolveWorkspaceRepoFromEnv } from '../runtime/workspace_repo.js';
7
+ import fs from 'node:fs';
8
+ import os from 'node:os';
9
+ import path from 'node:path';
10
+ /** Chunk size per batch request. */
11
+ export const BATCH_CHUNK_SIZE = 20;
12
+ const MAX_BATCH_SIZE_BYTES = 500 * 1024; // 500KB
13
+ export { resolveRepoFromPath } from '../runtime/workspace_repo.js';
14
+ function recordFailedArtifacts(target, files, seen) {
15
+ for (const file of files) {
16
+ const file_path = canonicalCursorUserStateVscdbPath(file.file_path);
17
+ const file_type = file.file_type || '';
18
+ const key = `${file_type}\t${file_path}`;
19
+ if (!file_type || seen.has(key))
20
+ continue;
21
+ seen.add(key);
22
+ target.push({ file_path, file_type });
23
+ }
24
+ }
25
+ function resolveApiBase(endpoint) {
26
+ try {
27
+ return new URL(endpoint).origin;
28
+ }
29
+ catch {
30
+ return endpoint.replace(/\/+$/, '');
31
+ }
32
+ }
33
+ function estimateConfigFileSize(configFile) {
34
+ if (configFile.collect_style === 'metadata')
35
+ return 300;
36
+ const contentStr = typeof configFile.raw_content === 'string'
37
+ ? configFile.raw_content
38
+ : JSON.stringify(configFile.raw_content || {});
39
+ return (configFile.file_type?.length || 0) + (configFile.file_path?.length || 0) + contentStr.length + 80 + Math.ceil(contentStr.length * 0.3);
40
+ }
41
+ function readOrganizationUuid() {
42
+ const envValue = (process.env.OPTIMUS_ORGANIZATION_UUID || '').trim();
43
+ if (envValue)
44
+ return envValue;
45
+ try {
46
+ return fs.readFileSync(path.join(os.homedir(), '.optimuslabs', 'management', 'org', 'organization-uuid.txt'), 'utf8').trim();
47
+ }
48
+ catch {
49
+ return '';
50
+ }
51
+ }
52
+ function buildBatchChunks(configFiles, basePayloadSize) {
53
+ const chunks = [];
54
+ let currentChunk = [];
55
+ let currentChunkSize = basePayloadSize;
56
+ for (const configFile of configFiles) {
57
+ const fileSize = estimateConfigFileSize(configFile);
58
+ if ((currentChunkSize + fileSize > MAX_BATCH_SIZE_BYTES || currentChunk.length >= BATCH_CHUNK_SIZE) && currentChunk.length > 0) {
59
+ chunks.push(currentChunk);
60
+ currentChunk = [];
61
+ currentChunkSize = basePayloadSize;
62
+ }
63
+ if (fileSize > MAX_BATCH_SIZE_BYTES) {
64
+ const note = configFile.collect_style === 'metadata' ? ' (metadata only)' : '';
65
+ hookRunLog(`warning: file ${configFile.file_path} is very large (${Math.round(fileSize / 1024)}KB)${note}`);
66
+ if (currentChunk.length > 0) {
67
+ chunks.push(currentChunk);
68
+ currentChunk = [];
69
+ currentChunkSize = basePayloadSize;
70
+ }
71
+ chunks.push([configFile]);
72
+ continue;
73
+ }
74
+ currentChunk.push(configFile);
75
+ currentChunkSize += fileSize;
76
+ }
77
+ if (currentChunk.length > 0)
78
+ chunks.push(currentChunk);
79
+ return chunks;
80
+ }
81
+ function buildChunkBody(chunk, hardwareUuid, authKey, hookRequestId, metadata) {
82
+ const config_files = chunk.map((c) => ({
83
+ file_type: c.file_type,
84
+ file_path: canonicalCursorUserStateVscdbPath(c.file_path),
85
+ raw_content: c.raw_content,
86
+ }));
87
+ const payload = { hardware_uuid: hardwareUuid, metadata, config_files };
88
+ if (hookRequestId != null)
89
+ payload.hook_request_id = hookRequestId;
90
+ const signature = createSignature(payload, authKey.key);
91
+ return { ...payload, signature, key_id: authKey.key_id || '' };
92
+ }
93
+ function splitChunk(chunks, chunkIndex) {
94
+ const chunk = chunks[chunkIndex];
95
+ const half = Math.ceil(chunk.length / 2);
96
+ chunks[chunkIndex] = chunk.slice(0, half);
97
+ chunks.splice(chunkIndex + 1, 0, chunk.slice(half));
98
+ }
99
+ async function sendConfigFilesBatch(configFiles, hardwareUuid, authKey, hookRequestId, ingestSessionId) {
100
+ const endpoint = loadEndpointBase();
101
+ const apiUrl = `${resolveApiBase(endpoint)}/endpoint_security/log-config-files/`;
102
+ const metadata = { org_identifier: process.env.GITHUB_ORG || process.env.GH_ORG || '', organization_uuid: readOrganizationUuid(), repo_identifier: resolveWorkspaceRepoFromEnv() };
103
+ const basePayloadSize = JSON.stringify({ hardware_uuid: hardwareUuid, metadata }).length + 800;
104
+ const chunks = buildBatchChunks(configFiles, basePayloadSize);
105
+ const totals = { accepted: 0, failed: 0 };
106
+ const failedArtifacts = [];
107
+ const failedArtifactKeys = new Set();
108
+ let chunkIndex = 0;
109
+ while (chunkIndex < chunks.length) {
110
+ let chunk = chunks[chunkIndex];
111
+ let body = buildChunkBody(chunk, hardwareUuid, authKey, hookRequestId, metadata);
112
+ if (ingestSessionId) {
113
+ const payload = { hardware_uuid: hardwareUuid, metadata, config_files: body.config_files, ingest_session_id: ingestSessionId, ...(hookRequestId != null ? { hook_request_id: hookRequestId } : {}) };
114
+ const signature = createSignature(payload, authKey.key);
115
+ body = { ...payload, signature, key_id: authKey.key_id || '' };
116
+ }
117
+ const bodySize = new TextEncoder().encode(JSON.stringify(body)).length;
118
+ // Pre-send size check: split if too large
119
+ if (bodySize > MAX_BATCH_SIZE_BYTES && chunk.length > 1) {
120
+ hookRunLog(`batch chunk too large (${Math.round(bodySize / 1024)}KB), splitting ${chunk.length} files`);
121
+ splitChunk(chunks, chunkIndex);
122
+ // Don't increment — retry with the smaller first half
123
+ continue;
124
+ }
125
+ const fileInfo = chunk.length === 1 ? `: ${chunk[0].file_path}` : chunk.length <= 3 ? `: ${chunk.map((f) => f.file_path.split('/').pop()).join(', ')}` : '';
126
+ hookRunLog(`batch send ${chunkIndex + 1}/${chunks.length}: ${chunk.length} file(s) (~${Math.round(bodySize / 1024)}KB)${fileInfo}`);
127
+ const timeoutMs = Math.min(20000 + Math.ceil(bodySize / (1024 * 1024)) * 15000, 90000);
128
+ try {
129
+ const response = (await postStartupPayload(apiUrl, body, timeoutMs));
130
+ if (response.status === 'accepted') {
131
+ totals.accepted += typeof response.accepted === 'number' ? response.accepted : chunk.length;
132
+ const failedList = Array.isArray(response.failed) ? response.failed : [];
133
+ totals.failed += failedList.length;
134
+ if (failedList.length > 0) {
135
+ hookRunLog(`batch chunk failed items: ${JSON.stringify(failedList.slice(0, 3))}`);
136
+ for (const item of failedList) {
137
+ const idx = typeof item.index === 'number'
138
+ ? item.index
139
+ : -1;
140
+ if (idx >= 0 && idx < chunk.length) {
141
+ recordFailedArtifacts(failedArtifacts, [chunk[idx]], failedArtifactKeys);
142
+ }
143
+ }
144
+ }
145
+ }
146
+ else {
147
+ totals.failed += chunk.length;
148
+ recordFailedArtifacts(failedArtifacts, chunk, failedArtifactKeys);
149
+ hookRunLog(`batch chunk failed: ${response.error || response.message || response.status}`);
150
+ }
151
+ }
152
+ catch (error) {
153
+ const errorMessage = error instanceof Error ? error.message : String(error);
154
+ hookRunLog(`batch chunk error: ${errorMessage}`);
155
+ totals.failed += chunk.length;
156
+ recordFailedArtifacts(failedArtifacts, chunk, failedArtifactKeys);
157
+ }
158
+ chunkIndex++;
159
+ }
160
+ return { ...totals, failedArtifacts };
161
+ }
162
+ async function sendIngestSessionStart(hardwareUuid, authKey) {
163
+ const endpoint = loadEndpointBase();
164
+ const apiUrl = `${resolveApiBase(endpoint)}/endpoint_security/ingest-session/start/`;
165
+ const payload = { hardware_uuid: hardwareUuid, action: 'ingest_session_start' };
166
+ const signature = createSignature(payload, authKey.key);
167
+ const body = { ...payload, signature, key_id: authKey.key_id || '' };
168
+ try {
169
+ const response = (await postStartupPayload(apiUrl, body));
170
+ if (response.status === 'accepted' && typeof response.ingest_session_id === 'string')
171
+ return response.ingest_session_id;
172
+ hookRunLog(`ingest-session start failed: ${response.error || response.status || 'unknown'}`);
173
+ return null;
174
+ }
175
+ catch (error) {
176
+ hookRunLog(`ingest-session start error: ${error instanceof Error ? error.message : String(error)}`);
177
+ return null;
178
+ }
179
+ }
180
+ async function sendIngestSessionFinish(hardwareUuid, authKey, ingestSessionId, failedUploads = [], worktreeReport = []) {
181
+ const endpoint = loadEndpointBase();
182
+ const apiUrl = `${resolveApiBase(endpoint)}/endpoint_security/ingest-session/finish/`;
183
+ const failed_uploads = [...failedUploads]
184
+ .map(({ file_path, file_type }) => ({ file_path, file_type }))
185
+ .sort((a, b) => a.file_path.localeCompare(b.file_path) || a.file_type.localeCompare(b.file_type));
186
+ const payload = {
187
+ hardware_uuid: hardwareUuid,
188
+ action: 'ingest_session_finish',
189
+ ingest_session_id: ingestSessionId,
190
+ };
191
+ if (failed_uploads.length > 0) {
192
+ payload.failed_uploads = failed_uploads;
193
+ }
194
+ const worktree_report = [...worktreeReport]
195
+ .map((e) => ({
196
+ worktree_root: e.worktree_root,
197
+ worktree_name: e.worktree_name,
198
+ repo_identifier: e.repo_identifier,
199
+ layout: e.layout,
200
+ last_modified: e.last_modified,
201
+ }))
202
+ .sort((a, b) => a.worktree_root.localeCompare(b.worktree_root));
203
+ // Always send the key (may be []) so the server prunes stale worktrees when none are active.
204
+ payload.worktree_report = worktree_report;
205
+ const signature = createSignature(payload, authKey.key);
206
+ const body = { ...payload, signature, key_id: authKey.key_id || '' };
207
+ try {
208
+ const response = (await postStartupPayload(apiUrl, body, 120000));
209
+ if (response.status === 'accepted')
210
+ return true;
211
+ hookRunLog(`ingest-session finish failed: ${response.error || response.status || 'unknown'}`);
212
+ return false;
213
+ }
214
+ catch (error) {
215
+ hookRunLog(`ingest-session finish error: ${error instanceof Error ? error.message : String(error)}`);
216
+ return false;
217
+ }
218
+ }
219
+ async function sendConfigFile(configFile, hardwareUuid, authKey, repoIdentifier) {
220
+ const endpoint = loadEndpointBase();
221
+ const apiUrl = `${resolveApiBase(endpoint)}/endpoint_security/log-config-file/`;
222
+ const uploadPath = canonicalCursorUserStateVscdbPath(configFile.file_path);
223
+ const payload = { hardware_uuid: hardwareUuid, file_type: configFile.file_type, file_path: uploadPath, raw_content: configFile.raw_content };
224
+ const signature = createSignature(payload, authKey.key);
225
+ const body = { ...payload, signature, key_id: authKey.key_id || '', metadata: { org_identifier: process.env.GITHUB_ORG || process.env.GH_ORG || '', organization_uuid: readOrganizationUuid(), repo_identifier: repoIdentifier ?? resolveWorkspaceRepoFromEnv() } };
226
+ try {
227
+ const response = await postStartupPayload(apiUrl, body);
228
+ if (response.status !== 'accepted') {
229
+ hookRunLog(`sendConfigFile: rejected status=${response.status} error=${response.error ?? ''} path=${uploadPath}`);
230
+ }
231
+ return response.status === 'accepted';
232
+ }
233
+ catch (err) {
234
+ hookRunLog(`sendConfigFile: request failed path=${uploadPath} err=${err instanceof Error ? err.message : String(err)}`);
235
+ return false;
236
+ }
237
+ }
238
+ async function sendHookRequestCreate(hardwareUuid, authKey, hookType, workspaceRepo) {
239
+ const endpoint = loadEndpointBase();
240
+ const apiUrl = `${resolveApiBase(endpoint)}/endpoint_security/hook-request/`;
241
+ const workspace_repo = String(workspaceRepo || '').trim().slice(0, 512);
242
+ const payload = { hardware_uuid: hardwareUuid, hook_type: hookType, workspace_repo, manifest: [] };
243
+ const signature = createSignature(payload, authKey.key);
244
+ const body = { ...payload, signature, key_id: authKey.key_id || '' };
245
+ try {
246
+ hookRunLog(`hook-request create (step 1) posting to ${apiUrl}`);
247
+ const response = (await postStartupPayload(apiUrl, body));
248
+ if (response.status === 'accepted' && typeof response.id === 'number') {
249
+ hookRunLog(`hook-request created id=${response.id}`);
250
+ return response.id;
251
+ }
252
+ hookRunLog(`hook-request create failed: ${response.error || response.status}`);
253
+ return null;
254
+ }
255
+ catch (error) {
256
+ hookRunLog(`hook-request create error: ${error instanceof Error ? error.message : String(error)}`);
257
+ return null;
258
+ }
259
+ }
260
+ async function sendHookRequestUpdateManifest(hardwareUuid, authKey, hookRequestId, manifest, hookLog) {
261
+ const endpoint = loadEndpointBase();
262
+ const apiUrl = `${resolveApiBase(endpoint)}/endpoint_security/hook-request/${hookRequestId}/`;
263
+ const manifestNormalized = manifest.slice(0, 1000).map((x) => (x != null && typeof x === 'string' ? x.trim() : String(x)).slice(0, 2048));
264
+ const payload = { hardware_uuid: hardwareUuid, hook_request_id: hookRequestId, manifest: manifestNormalized };
265
+ const signature = createSignature(payload, authKey.key);
266
+ const body = { ...payload, signature, key_id: authKey.key_id || '' };
267
+ if (hookLog)
268
+ body.hook_log = hookLog;
269
+ try {
270
+ const data = (await patchPayload(apiUrl, body));
271
+ if (data.status === 'accepted') {
272
+ hookRunLog(`hook-request manifest updated (${manifestNormalized.length} paths)`);
273
+ return true;
274
+ }
275
+ hookRunLog(`hook-request update failed: ${data.error ?? data.status ?? 'unknown'}`);
276
+ return false;
277
+ }
278
+ catch (error) {
279
+ hookRunLog(`hook-request update error: ${error instanceof Error ? error.message : String(error)}`);
280
+ return false;
281
+ }
282
+ }
283
+ export { sendConfigFilesBatch, sendConfigFile, sendHookRequestCreate, sendHookRequestUpdateManifest, sendIngestSessionStart, sendIngestSessionFinish, estimateConfigFileSize };
@@ -0,0 +1,24 @@
1
+ import { getEndpointSource as getSharedEndpointSource, loadEndpointBase as loadSharedEndpointBase, } from "../../tofu.js";
2
+ /**
3
+ * log-llm-config historically only honored OPTIMUS_ENDPOINT (plus file / default).
4
+ * OPTIMUS_API_URL is reserved for optimus-tool-call; omit it here for backward compatibility.
5
+ */
6
+ function envWithoutOptimusApiUrl(env) {
7
+ const copy = { ...env };
8
+ delete copy.OPTIMUS_API_URL;
9
+ return copy;
10
+ }
11
+ export function loadEndpointBase(options) {
12
+ const env = envWithoutOptimusApiUrl(options?.env ?? process.env);
13
+ return loadSharedEndpointBase({ cwd: options?.cwd, env });
14
+ }
15
+ /** Where endpoint came from (for logging). Matches legacy semantics (never driven by OPTIMUS_API_URL alone). */
16
+ export function getEndpointSource(options) {
17
+ const env = envWithoutOptimusApiUrl(options?.env ?? process.env);
18
+ const source = getSharedEndpointSource({ cwd: options?.cwd, env });
19
+ if (source === "file")
20
+ return "file";
21
+ if (source === "default")
22
+ return "default";
23
+ return "env";
24
+ }
@@ -0,0 +1 @@
1
+ export { canonicalizePayload, createSignature } from '../../tofu.js';
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Sensitive paths audit: writes sensitive_paths_audit.txt listing which sensitive
4
+ * paths exist on this machine. NEVER reads or sends sensitive file contents—existence only.
5
+ *
6
+ * Path templates are fetched from the backend (GET api/file-path-registry/sensitive-paths-audit-candidates/).
7
+ * The audit is sent with the rest of the config files in log_config_files (same auth, same batch).
8
+ */
9
+ import { existsSync, writeFileSync, mkdirSync, statSync } from 'node:fs';
10
+ import { join } from 'node:path';
11
+ import { homedir } from 'node:os';
12
+ import { getSensitivePathsAuditCandidates } from './endpoint_client/index.js';
13
+ import { OPTIMUSLABS_LOGS_REL, LOG_GROUP_AUDIT } from './bootstrap_constants.js';
14
+ import { loadEndpointBase } from './log_config_files/sender/endpoint_config.js';
15
+ const AUDIT_FILENAME = 'sensitive_paths_audit.txt';
16
+ function expandPath(template, cwd) {
17
+ if (template.startsWith('~/')) {
18
+ return join(homedir(), template.slice(2));
19
+ }
20
+ if (template.startsWith('/')) {
21
+ return template;
22
+ }
23
+ return join(cwd, template);
24
+ }
25
+ /** True when path exists: directories always qualify; files must be non-empty (size > 0). */
26
+ function shouldIncludeSensitivePath(resolved) {
27
+ if (!existsSync(resolved)) {
28
+ return false;
29
+ }
30
+ try {
31
+ const st = statSync(resolved);
32
+ if (st.isDirectory()) {
33
+ return true;
34
+ }
35
+ if (st.isFile()) {
36
+ return st.size > 0;
37
+ }
38
+ return false;
39
+ }
40
+ catch {
41
+ return false;
42
+ }
43
+ }
44
+ /**
45
+ * Write sensitive_paths_audit.txt under outputDir. pathTemplates from backend (~ = home).
46
+ * Lists one path per line for paths that exist (files must be non-empty). No file contents are read.
47
+ * Overwrites the file on each run (same as hook_log.txt); does not append.
48
+ */
49
+ export function writeSensitivePathsAudit(outputDir, pathTemplates, cwd = process.cwd()) {
50
+ const existing = [];
51
+ for (const template of pathTemplates) {
52
+ const resolved = expandPath(template, cwd);
53
+ if (shouldIncludeSensitivePath(resolved)) {
54
+ existing.push(resolved);
55
+ }
56
+ }
57
+ mkdirSync(outputDir, { recursive: true });
58
+ const outPath = join(outputDir, AUDIT_FILENAME);
59
+ writeFileSync(outPath, existing.join('\n') + (existing.length ? '\n' : ''), 'utf8');
60
+ return existing;
61
+ }
62
+ /**
63
+ * Run audit: fetch candidates from backend, check existence, write file. Returns paths for sending.
64
+ * Used by log_config_files to include the audit in the same batch (same auth).
65
+ */
66
+ export async function runSensitivePathsAudit(endpointBase, outputDir = join(homedir(), OPTIMUSLABS_LOGS_REL, LOG_GROUP_AUDIT), cwd = process.cwd()) {
67
+ const response = await getSensitivePathsAuditCandidates(endpointBase);
68
+ const pathTemplates = response?.paths ?? [];
69
+ if (pathTemplates.length === 0 && !response) {
70
+ console.warn('Could not fetch sensitive path candidates from backend; writing empty audit.');
71
+ }
72
+ const paths = writeSensitivePathsAudit(outputDir, pathTemplates, cwd);
73
+ const outPath = join(outputDir, AUDIT_FILENAME);
74
+ console.log(`Sensitive paths audit written to ${outPath} (${paths.length} path(s) present).`);
75
+ return { paths };
76
+ }
77
+ export async function main() {
78
+ const outputDir = join(homedir(), OPTIMUSLABS_LOGS_REL, LOG_GROUP_AUDIT);
79
+ const endpointBase = loadEndpointBase();
80
+ await runSensitivePathsAudit(endpointBase, outputDir, process.cwd());
81
+ process.exit(0);
82
+ }
83
+ if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.includes('log_sensitive_paths_audit')) {
84
+ main().catch((err) => {
85
+ console.error('Sensitive paths audit failed:', err);
86
+ process.exit(1);
87
+ });
88
+ }
89
+ export { AUDIT_FILENAME };
@@ -0,0 +1,37 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { OPTIMUSLABS_MANAGEMENT_REL } from '../bootstrap_constants.js';
4
+ import { loadEndpointBase } from '../log_config_files/sender/endpoint_config.js';
5
+ import { buildStartupEndpointUrl, normalizeBaseUrl } from '../tofu.js';
6
+ const AUTH_KEY_RELATIVE_PATH = path.join(OPTIMUSLABS_MANAGEMENT_REL, 'auth', 'auth_key.txt');
7
+ const getAuthKeyPath = () => {
8
+ const homeDir = process.env.HOME || process.env.USERPROFILE;
9
+ if (!homeDir)
10
+ return null;
11
+ return path.join(homeDir, AUTH_KEY_RELATIVE_PATH);
12
+ };
13
+ const writeAuthKey = (key, keyId, hardwareUuid) => {
14
+ const authPath = getAuthKeyPath();
15
+ if (!authPath) {
16
+ console.warn('Unable to determine home directory; cannot store auth key.');
17
+ return;
18
+ }
19
+ mkdirSync(path.dirname(authPath), { recursive: true, mode: 0o700 });
20
+ writeFileSync(authPath, JSON.stringify({ key, key_id: keyId ?? null, hardware_uuid: hardwareUuid, stored_at: new Date().toISOString() }, null, 2), { encoding: 'utf8', mode: 0o600 });
21
+ console.log(`Stored auth key at ${authPath}`);
22
+ };
23
+ const readStoredAuthKey = () => {
24
+ const authPath = getAuthKeyPath();
25
+ if (!authPath || !existsSync(authPath))
26
+ return null;
27
+ try {
28
+ const parsed = JSON.parse(readFileSync(authPath, 'utf8'));
29
+ if (parsed?.key && parsed?.hardware_uuid)
30
+ return parsed;
31
+ }
32
+ catch (error) {
33
+ console.warn(`Unable to read stored auth key: ${error.message}`);
34
+ }
35
+ return null;
36
+ };
37
+ export { getAuthKeyPath, writeAuthKey, readStoredAuthKey, loadEndpointBase, normalizeBaseUrl, buildStartupEndpointUrl, };
@@ -0,0 +1,35 @@
1
+ import { execSync } from 'node:child_process';
2
+ const readCommandOutput = (command) => {
3
+ try {
4
+ // execSync is used here with static strings only (no user input) - safe from injection
5
+ return execSync(command, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
6
+ }
7
+ catch {
8
+ return '';
9
+ }
10
+ };
11
+ const readHardwareUuidFromCommands = () => {
12
+ const ioregOutput = readCommandOutput('ioreg -rd1 -c IOPlatformExpertDevice');
13
+ const ioregMatch = ioregOutput.match(/"IOPlatformUUID"\s*=\s*"([^"]+)"/i);
14
+ if (ioregMatch?.[1])
15
+ return ioregMatch[1];
16
+ const profilerOutput = readCommandOutput('system_profiler SPHardwareDataType');
17
+ const profilerMatch = profilerOutput.match(/Hardware UUID:\s*([A-F0-9-]+)/i);
18
+ if (profilerMatch?.[1])
19
+ return profilerMatch[1];
20
+ throw new Error('Unable to determine hardware UUID via ioreg or system_profiler.');
21
+ };
22
+ const normalizeHardwareUuid = (raw) => {
23
+ const trimmed = raw.trim();
24
+ const match = trimmed.match(/[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}/);
25
+ if (match)
26
+ return match[0];
27
+ return trimmed.replace(/^"+|"+$/g, '');
28
+ };
29
+ const resolveHardwareUuid = () => {
30
+ const envUuid = process.env.OPTIMUS_HARDWARE_UUID?.trim();
31
+ if (envUuid)
32
+ return normalizeHardwareUuid(envUuid);
33
+ return normalizeHardwareUuid(readHardwareUuidFromCommands());
34
+ };
35
+ export { readCommandOutput, readHardwareUuidFromCommands, normalizeHardwareUuid, resolveHardwareUuid };
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ // Barrel — all public exports from sub-modules under ./log_uuid/.
3
+ export { resolveHardwareUuid } from './hardware_uuid.js';
4
+ export { getAuthKeyPath, writeAuthKey, readStoredAuthKey, loadEndpointBase, normalizeBaseUrl } from './auth_key_store.js';
5
+ export { getMetadata, createSignature, maybeSendToEndpoint, main } from './startup_sender.js';
6
+ export { buildLogUuidScriptLines, renderLogUuidScript } from './log_uuid_helper.js';
7
+ // Entry point guard (when invoked directly via npx or node)
8
+ if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.includes('log_uuid')) {
9
+ const { main } = await import('./startup_sender.js');
10
+ void main();
11
+ }
@@ -0,0 +1,30 @@
1
+ export const buildLogUuidScriptLines = () => [
2
+ '#!/usr/bin/env bash',
3
+ 'set -euo pipefail',
4
+ '',
5
+ 'output_file="$(pwd)/uuid.txt"',
6
+ 'timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")',
7
+ 'if command -v ioreg >/dev/null 2>&1; then',
8
+ ' uuid_value=$(ioreg -rd1 -c IOPlatformExpertDevice | awk \'/IOPlatformUUID/ {gsub(/\\"/, "", $3); print $3}\')',
9
+ 'elif command -v system_profiler >/dev/null 2>&1; then',
10
+ ' uuid_value=$(system_profiler SPHardwareDataType | awk \'/Hardware UUID/ {print $3}\')',
11
+ 'else',
12
+ ' echo "Unable to locate hardware UUID utilities (ioreg/system_profiler)" >&2',
13
+ ' exit 1',
14
+ 'fi',
15
+ '',
16
+ 'if [ -z "$uuid_value" ]; then',
17
+ ' echo "Hardware UUID could not be determined" >&2',
18
+ ' exit 1',
19
+ 'fi',
20
+ '',
21
+ 'cat <<EOF | tee "$output_file"',
22
+ '===== Optimus UUID =====',
23
+ 'Timestamp: $timestamp',
24
+ 'Directory: $(pwd)',
25
+ 'UUID: $uuid_value',
26
+ 'EOF',
27
+ '',
28
+ 'echo "UUID details saved to $output_file"'
29
+ ];
30
+ export const renderLogUuidScript = () => buildLogUuidScriptLines().join('\n');