@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.
- package/README.md +46 -0
- package/dist/apply_deferred_vscdb.js +38 -0
- package/dist/bootstrap_constants.js +15 -0
- package/dist/cli/bash_script_generator.js +95 -0
- package/dist/cli.js +144 -0
- package/dist/cli_invocation_match.js +30 -0
- package/dist/compliance_check_runner.js +57 -0
- package/dist/compliance_prompt_gate.js +396 -0
- package/dist/dialog_prefs_cli.js +109 -0
- package/dist/endpoint_client/compress_payload.js +25 -0
- package/dist/endpoint_client/http_transport.js +90 -0
- package/dist/endpoint_client/index.js +3 -0
- package/dist/endpoint_client/registry_api.js +41 -0
- package/dist/endpoint_client/startup_api.js +44 -0
- package/dist/endpoint_client/types.js +4 -0
- package/dist/execute_trusted_restarts.js +60 -0
- package/dist/log_config_files/auth/auth_flow.js +22 -0
- package/dist/log_config_files/auth/auth_key_store.js +14 -0
- package/dist/log_config_files/collection/claude_desktop_extensions_collector.js +248 -0
- package/dist/log_config_files/collection/claude_known_projects_collector.js +42 -0
- package/dist/log_config_files/collection/claude_token_usage_collector.js +203 -0
- package/dist/log_config_files/collection/codex_token_usage_collector.js +342 -0
- package/dist/log_config_files/collection/config_collector.js +245 -0
- package/dist/log_config_files/collection/copilot_token_usage_collector.js +167 -0
- package/dist/log_config_files/collection/cowork_desktop_version_collector.js +60 -0
- package/dist/log_config_files/collection/cowork_session_whitelist.js +89 -0
- package/dist/log_config_files/collection/cursor_project_mcp_collector.js +230 -0
- package/dist/log_config_files/collection/cursor_project_workspace_activity.js +73 -0
- package/dist/log_config_files/collection/cursor_token_usage_collector.js +192 -0
- package/dist/log_config_files/collection/directory_collector.js +126 -0
- package/dist/log_config_files/collection/enrichment_helpers.js +53 -0
- package/dist/log_config_files/collection/ensure_cursor_user_settings_snapshot.js +50 -0
- package/dist/log_config_files/collection/file_type_rules.js +47 -0
- package/dist/log_config_files/collection/grok_cli_version_collector.js +53 -0
- package/dist/log_config_files/collection/hermes_token_usage_collector.js +241 -0
- package/dist/log_config_files/collection/mcp_tool_collector.js +106 -0
- package/dist/log_config_files/collection/metadata_merge.js +43 -0
- package/dist/log_config_files/collection/openclaw_helpers.js +55 -0
- package/dist/log_config_files/collection/openclaw_token_usage_collector.js +223 -0
- package/dist/log_config_files/collection/opencode_token_usage_collector.js +177 -0
- package/dist/log_config_files/collection/pi_token_usage_collector.js +239 -0
- package/dist/log_config_files/collection/plugin_collector.js +223 -0
- package/dist/log_config_files/collection/plugin_version_helpers.js +37 -0
- package/dist/log_config_files/collection/skills_cli_collector.js +162 -0
- package/dist/log_config_files/index.js +19 -0
- package/dist/log_config_files/paths/path_constants_helpers.js +71 -0
- package/dist/log_config_files/paths/pattern_resolver.js +326 -0
- package/dist/log_config_files/readers/composer_shadow_merge.js +67 -0
- package/dist/log_config_files/readers/cursor_shadow_merge_ruleset.js +46 -0
- package/dist/log_config_files/readers/file_readers.js +196 -0
- package/dist/log_config_files/readers/vscdb_config_builder.js +148 -0
- package/dist/log_config_files/readers/vscdb_reactive_storage.js +62 -0
- package/dist/log_config_files/readers/vscdb_reader.js +337 -0
- package/dist/log_config_files/runtime/client_event_reporter.js +347 -0
- package/dist/log_config_files/runtime/compliance_check.js +1013 -0
- package/dist/log_config_files/runtime/compliance_session_log.js +378 -0
- package/dist/log_config_files/runtime/dialog_preferences.js +31 -0
- package/dist/log_config_files/runtime/hardware_uuid.js +36 -0
- package/dist/log_config_files/runtime/hook_logger.js +267 -0
- package/dist/log_config_files/runtime/hook_type_for_request.js +30 -0
- package/dist/log_config_files/runtime/log_metadata.js +122 -0
- package/dist/log_config_files/runtime/main_runner.js +376 -0
- package/dist/log_config_files/runtime/management_storage.js +144 -0
- package/dist/log_config_files/runtime/ops_target_path.js +18 -0
- package/dist/log_config_files/runtime/remediation_apply_tracking.js +152 -0
- package/dist/log_config_files/runtime/remediation_config_path.js +140 -0
- package/dist/log_config_files/runtime/remediation_sync.js +1705 -0
- package/dist/log_config_files/runtime/secret_regex_scan.js +126 -0
- package/dist/log_config_files/runtime/sqlite_binary.js +124 -0
- package/dist/log_config_files/runtime/trusted_restarts.js +113 -0
- package/dist/log_config_files/runtime/workspace_repo.js +170 -0
- package/dist/log_config_files/runtime/worktree_absent.js +50 -0
- package/dist/log_config_files/runtime/worktree_scanner.js +137 -0
- package/dist/log_config_files/sender/batch_sender.js +283 -0
- package/dist/log_config_files/sender/endpoint_config.js +24 -0
- package/dist/log_config_files/sender/signing.js +1 -0
- package/dist/log_sensitive_paths_audit.js +89 -0
- package/dist/log_uuid/auth_key_store.js +37 -0
- package/dist/log_uuid/hardware_uuid.js +35 -0
- package/dist/log_uuid/index.js +11 -0
- package/dist/log_uuid/log_uuid_helper.js +30 -0
- package/dist/log_uuid/startup_sender.js +131 -0
- package/dist/log_uuid/user_profile.js +192 -0
- package/dist/remediation_change_preview.js +182 -0
- package/dist/tofu.js +22 -0
- package/dist/tofu_environment.js +36 -0
- package/dist/types/config_file_types.js +1 -0
- package/package.json +67 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
const HOME_DIR = homedir();
|
|
5
|
+
import { getFileCollectionPatterns, FILE_PATH_REGISTRY_FILE_PATTERNS_PATH } from '../../endpoint_client/index.js';
|
|
6
|
+
import { OPTIMUSLABS_LOGS_REL, LOG_GROUP_AUDIT } from '../../bootstrap_constants.js';
|
|
7
|
+
import { runSensitivePathsAudit } from '../../log_sensitive_paths_audit.js';
|
|
8
|
+
import { loadEndpointBase, getEndpointSource } from '../sender/endpoint_config.js';
|
|
9
|
+
import { hookLogReplace, hookRunLog, hookLogError, readHookLog } from './hook_logger.js';
|
|
10
|
+
import { getStoryId, flushBufferedClientEvents, reportFunctionalRun, reportFunctionalFailureAwait, } from './client_event_reporter.js';
|
|
11
|
+
import { resolveHookTypeFromEnv } from './hook_type_for_request.js';
|
|
12
|
+
import { resolveHardwareUuid } from './hardware_uuid.js';
|
|
13
|
+
import { ensureAuthentication } from '../auth/auth_flow.js';
|
|
14
|
+
import { readJSONFile, readMarkdownFile } from '../readers/file_readers.js';
|
|
15
|
+
import { isVscdbVirtualPath, tryReadVscdbVirtualFile, summarizeComposerPayloadForDiagnostics, } from '../readers/vscdb_config_builder.js';
|
|
16
|
+
import { persistVscdbComposerContractFromPatternsResponse } from '../readers/vscdb_reader.js';
|
|
17
|
+
import { collectConfigFilesFromPatterns, collectMcpToolFiles, collectConfigFilesFromInstalledPlugins, collectPluginCacheMcpFiles, collectMcpFromClaudeJsonProjects, collectClaudeTokenUsage, collectCopilotTokenUsage, collectOpencodeTokenUsage, collectCodexTokenUsage, collectCursorTokenUsage, collectHermesTokenUsage, collectOpenclawTokenUsage, collectPiTokenUsage, collectCoworkDesktopVersion, collectGrokCliVersion, collectClaudeDesktopExtensionManifests, collectClaudeDesktopExtensionSettingsFiles, enrichClaudeDesktopExtensionsInstallationsUpload, determineFileTypeFromPath, } from '../collection/config_collector.js';
|
|
18
|
+
import { ensureCursorUserSettingsSnapshotInBatch } from '../collection/ensure_cursor_user_settings_snapshot.js';
|
|
19
|
+
import { collectSkillsCliInstalled } from '../collection/skills_cli_collector.js';
|
|
20
|
+
import { collectWorkspaceVscdbs } from '../collection/mcp_tool_collector.js';
|
|
21
|
+
import { collectCursorProjectWorkspaceMcpConfigs } from '../collection/cursor_project_mcp_collector.js';
|
|
22
|
+
import { normalizePathSkipPrefixes } from '../paths/pattern_resolver.js';
|
|
23
|
+
import { sendConfigFile, sendConfigFilesBatch, sendHookRequestCreate, sendHookRequestUpdateManifest, sendIngestSessionStart, sendIngestSessionFinish, BATCH_CHUNK_SIZE } from '../sender/batch_sender.js';
|
|
24
|
+
import { canonicalCursorUserStateVscdbPath } from './remediation_config_path.js';
|
|
25
|
+
import { ensureWorkspaceRepoEnv, resolveProjectRoot } from './workspace_repo.js';
|
|
26
|
+
import { scanActiveWorktrees, activeWorktreeRootSet, filterConfigFilesToActiveWorktrees, } from './worktree_scanner.js';
|
|
27
|
+
import { createSignature, canonicalizePayload } from '../sender/signing.js';
|
|
28
|
+
const PROJECT_ROOT = resolveProjectRoot();
|
|
29
|
+
async function collectAllConfigFiles(endpointBase) {
|
|
30
|
+
const patternsUrl = `${endpointBase.replace(/\/+$/, '')}${FILE_PATH_REGISTRY_FILE_PATTERNS_PATH}`;
|
|
31
|
+
hookRunLog(`fetching patterns from ${patternsUrl}`);
|
|
32
|
+
const patternsResponse = await getFileCollectionPatterns(endpointBase);
|
|
33
|
+
const n = patternsResponse?.patterns?.length ?? 0;
|
|
34
|
+
hookRunLog(`patterns: ${n} pattern(s) received`);
|
|
35
|
+
if (!patternsResponse?.patterns?.length) {
|
|
36
|
+
const msg = 'File collection requires patterns from the API; none received. Check endpoint and file-path-registry.';
|
|
37
|
+
hookRunLog(msg);
|
|
38
|
+
throw new Error(msg);
|
|
39
|
+
}
|
|
40
|
+
persistVscdbComposerContractFromPatternsResponse(patternsResponse);
|
|
41
|
+
hookRunLog(`scanning config files`);
|
|
42
|
+
const configFiles = collectConfigFilesFromPatterns(patternsResponse.patterns, PROJECT_ROOT, hookRunLog, {
|
|
43
|
+
vscdbEntrySpecs: patternsResponse.vscdb_entry_specs,
|
|
44
|
+
vscdb_read_queries: patternsResponse.vscdb_read_queries,
|
|
45
|
+
vscdb_merge_from_composer_state: patternsResponse.vscdb_merge_from_composer_state,
|
|
46
|
+
client_path_constants: patternsResponse.client_path_constants,
|
|
47
|
+
path_resolution_specs: patternsResponse.path_resolution_specs,
|
|
48
|
+
home_recurse_skip_dirs: patternsResponse.home_recurse_skip_dirs,
|
|
49
|
+
absolute_path_prefixes: patternsResponse.absolute_path_prefixes,
|
|
50
|
+
mcp_tool_glob_spec: patternsResponse.mcp_tool_glob_spec ?? null,
|
|
51
|
+
});
|
|
52
|
+
hookRunLog(`scanning MCP tool cache`);
|
|
53
|
+
const mcpToolGlobPattern = patternsResponse.patterns.find((p) => p.path_resolution === 'mcp_tool_glob');
|
|
54
|
+
const mcpToolSkipPrefixes = normalizePathSkipPrefixes(mcpToolGlobPattern?.path_skip_prefixes);
|
|
55
|
+
const existingPaths = new Set(configFiles.map((c) => `${c.file_type}\t${c.file_path}`));
|
|
56
|
+
for (const m of collectMcpToolFiles(mcpToolSkipPrefixes, patternsResponse.client_path_constants)) {
|
|
57
|
+
const key = `${m.file_type}\t${m.file_path}`;
|
|
58
|
+
if (!existingPaths.has(key)) {
|
|
59
|
+
existingPaths.add(key);
|
|
60
|
+
configFiles.push(m);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
hookRunLog(`scanning Cursor project workspace MCP configs (~/.cursor/projects/*/mcp-cache.json)`);
|
|
64
|
+
for (const m of collectCursorProjectWorkspaceMcpConfigs(mcpToolSkipPrefixes, patternsResponse.client_path_constants, patternsResponse.workspace_vscdb_spec, HOME_DIR)) {
|
|
65
|
+
const key = `${m.file_type}\t${m.file_path}`;
|
|
66
|
+
if (!existingPaths.has(key)) {
|
|
67
|
+
existingPaths.add(key);
|
|
68
|
+
configFiles.push(m);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (patternsResponse.workspace_vscdb_spec) {
|
|
72
|
+
hookRunLog(`scanning Cursor workspace vscdb files`);
|
|
73
|
+
for (const m of collectWorkspaceVscdbs(patternsResponse.workspace_vscdb_spec)) {
|
|
74
|
+
const key = `${m.file_type}\t${m.file_path}`;
|
|
75
|
+
if (!existingPaths.has(key)) {
|
|
76
|
+
existingPaths.add(key);
|
|
77
|
+
configFiles.push(m);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
hookRunLog(`scanning installed plugins`);
|
|
82
|
+
if (!patternsResponse.client_path_constants)
|
|
83
|
+
throw new Error('client_path_constants required from API response');
|
|
84
|
+
configFiles.push(...collectConfigFilesFromInstalledPlugins(patternsResponse.client_path_constants));
|
|
85
|
+
hookRunLog(`scanning Claude plugin cache MCP manifests`);
|
|
86
|
+
for (const entry of collectPluginCacheMcpFiles(patternsResponse.client_path_constants, HOME_DIR)) {
|
|
87
|
+
const key = `${entry.file_type}\t${entry.file_path}`;
|
|
88
|
+
if (!existingPaths.has(key)) {
|
|
89
|
+
existingPaths.add(key);
|
|
90
|
+
configFiles.push(entry);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
hookRunLog(`scanning Claude known projects from ~/.claude.json`);
|
|
94
|
+
for (const entry of collectMcpFromClaudeJsonProjects(HOME_DIR)) {
|
|
95
|
+
const key = `${entry.file_type}\t${entry.file_path}`;
|
|
96
|
+
if (!existingPaths.has(key)) {
|
|
97
|
+
existingPaths.add(key);
|
|
98
|
+
configFiles.push(entry);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
hookRunLog(`aggregating Claude token usage from ~/.claude/projects transcripts`);
|
|
102
|
+
for (const entry of collectClaudeTokenUsage(HOME_DIR)) {
|
|
103
|
+
const key = `${entry.file_type}\t${entry.file_path}`;
|
|
104
|
+
if (!existingPaths.has(key)) {
|
|
105
|
+
existingPaths.add(key);
|
|
106
|
+
configFiles.push(entry);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
hookRunLog(`aggregating Copilot token usage from ~/.copilot/session-state transcripts`);
|
|
110
|
+
for (const entry of collectCopilotTokenUsage(HOME_DIR)) {
|
|
111
|
+
const key = `${entry.file_type}\t${entry.file_path}`;
|
|
112
|
+
if (!existingPaths.has(key)) {
|
|
113
|
+
existingPaths.add(key);
|
|
114
|
+
configFiles.push(entry);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
hookRunLog(`aggregating OpenCode token usage from ~/.local/share/opencode/opencode.db`);
|
|
118
|
+
for (const entry of collectOpencodeTokenUsage(HOME_DIR)) {
|
|
119
|
+
const key = `${entry.file_type}\t${entry.file_path}`;
|
|
120
|
+
if (!existingPaths.has(key)) {
|
|
121
|
+
existingPaths.add(key);
|
|
122
|
+
configFiles.push(entry);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
hookRunLog(`aggregating Codex token usage from ~/.codex/sessions rollouts`);
|
|
126
|
+
for (const entry of collectCodexTokenUsage(HOME_DIR)) {
|
|
127
|
+
const key = `${entry.file_type}\t${entry.file_path}`;
|
|
128
|
+
if (!existingPaths.has(key)) {
|
|
129
|
+
existingPaths.add(key);
|
|
130
|
+
configFiles.push(entry);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
hookRunLog(`aggregating Cursor token usage from state.vscdb`);
|
|
134
|
+
for (const entry of collectCursorTokenUsage(HOME_DIR)) {
|
|
135
|
+
const key = `${entry.file_type}\t${entry.file_path}`;
|
|
136
|
+
if (!existingPaths.has(key)) {
|
|
137
|
+
existingPaths.add(key);
|
|
138
|
+
configFiles.push(entry);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
hookRunLog(`aggregating Hermes token usage from ~/.hermes/state.db`);
|
|
142
|
+
for (const entry of collectHermesTokenUsage(HOME_DIR)) {
|
|
143
|
+
const key = `${entry.file_type}\t${entry.file_path}`;
|
|
144
|
+
if (!existingPaths.has(key)) {
|
|
145
|
+
existingPaths.add(key);
|
|
146
|
+
configFiles.push(entry);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
hookRunLog(`aggregating OpenClaw token usage from ~/.openclaw/agents/*/sessions/sessions.json`);
|
|
150
|
+
for (const entry of collectOpenclawTokenUsage(HOME_DIR)) {
|
|
151
|
+
const key = `${entry.file_type}\t${entry.file_path}`;
|
|
152
|
+
if (!existingPaths.has(key)) {
|
|
153
|
+
existingPaths.add(key);
|
|
154
|
+
configFiles.push(entry);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
hookRunLog(`aggregating Pi token usage from ~/.pi/agent/sessions transcripts`);
|
|
158
|
+
for (const entry of collectPiTokenUsage(HOME_DIR)) {
|
|
159
|
+
const key = `${entry.file_type}\t${entry.file_path}`;
|
|
160
|
+
if (!existingPaths.has(key)) {
|
|
161
|
+
existingPaths.add(key);
|
|
162
|
+
configFiles.push(entry);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
hookRunLog(`reading Claude Desktop app version for Cowork's Agent Profile`);
|
|
166
|
+
for (const entry of collectCoworkDesktopVersion(HOME_DIR)) {
|
|
167
|
+
const key = `${entry.file_type}\t${entry.file_path}`;
|
|
168
|
+
if (!existingPaths.has(key)) {
|
|
169
|
+
existingPaths.add(key);
|
|
170
|
+
configFiles.push(entry);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
hookRunLog(`reading Grok CLI version`);
|
|
174
|
+
for (const entry of collectGrokCliVersion(HOME_DIR)) {
|
|
175
|
+
const key = `${entry.file_type}\t${entry.file_path}`;
|
|
176
|
+
if (!existingPaths.has(key)) {
|
|
177
|
+
existingPaths.add(key);
|
|
178
|
+
configFiles.push(entry);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
hookRunLog(`merging disk-only Claude Desktop extensions into extensions-installations.json`);
|
|
182
|
+
const diskExtMerge = enrichClaudeDesktopExtensionsInstallationsUpload(configFiles, HOME_DIR);
|
|
183
|
+
hookRunLog(`claude desktop extensions: merged=${diskExtMerge.mergedCount} had_registry_upload=${diskExtMerge.hadRegistryUpload}`);
|
|
184
|
+
for (const entry of collectClaudeDesktopExtensionSettingsFiles(HOME_DIR)) {
|
|
185
|
+
const key = `${entry.file_type}\t${entry.file_path}`;
|
|
186
|
+
if (!existingPaths.has(key)) {
|
|
187
|
+
existingPaths.add(key);
|
|
188
|
+
configFiles.push(entry);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (!diskExtMerge.hadRegistryUpload && diskExtMerge.mergedCount > 0) {
|
|
192
|
+
hookRunLog(`scanning Claude Desktop extension manifests on disk (no registry upload in batch)`);
|
|
193
|
+
for (const entry of collectClaudeDesktopExtensionManifests(HOME_DIR)) {
|
|
194
|
+
const key = `${entry.file_type}\t${entry.file_path}`;
|
|
195
|
+
if (!existingPaths.has(key)) {
|
|
196
|
+
existingPaths.add(key);
|
|
197
|
+
configFiles.push(entry);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const skillsCliEntry = collectSkillsCliInstalled(PROJECT_ROOT, hookRunLog);
|
|
202
|
+
if (skillsCliEntry) {
|
|
203
|
+
const key = `${skillsCliEntry.file_type}\t${skillsCliEntry.file_path}`;
|
|
204
|
+
if (!existingPaths.has(key)) {
|
|
205
|
+
existingPaths.add(key);
|
|
206
|
+
configFiles.push(skillsCliEntry);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const worktreeReport = scanActiveWorktrees(PROJECT_ROOT, HOME_DIR);
|
|
210
|
+
const activeRoots = activeWorktreeRootSet(worktreeReport);
|
|
211
|
+
const beforeFilter = configFiles.length;
|
|
212
|
+
const filtered = filterConfigFilesToActiveWorktrees(configFiles, activeRoots);
|
|
213
|
+
hookRunLog(`worktree_scan: active=${worktreeReport.length} config_files ${beforeFilter}→${filtered.length}`);
|
|
214
|
+
return { configFiles: filtered, worktreeReport };
|
|
215
|
+
}
|
|
216
|
+
async function addSensitivePathsAudit(endpointBase, configFiles) {
|
|
217
|
+
hookRunLog(`running sensitive paths audit`);
|
|
218
|
+
const auditOutputDir = join(homedir(), OPTIMUSLABS_LOGS_REL, LOG_GROUP_AUDIT);
|
|
219
|
+
try {
|
|
220
|
+
const auditResult = await runSensitivePathsAudit(endpointBase, auditOutputDir, PROJECT_ROOT);
|
|
221
|
+
hookRunLog(`sensitive_paths_audit: ${auditResult.paths.length} path(s)`);
|
|
222
|
+
configFiles.push({ file_type: 'sensitive_paths_audit', file_path: 'sensitive_paths_audit.txt', raw_content: { paths: auditResult.paths } });
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
hookRunLog(`sensitive_paths_audit_error: ${err instanceof Error ? err.message : String(err)}`);
|
|
226
|
+
hookLogError('sensitive_paths_audit', err, { endpoint: endpointBase, output_dir: auditOutputDir });
|
|
227
|
+
configFiles.push({ file_type: 'sensitive_paths_audit', file_path: 'sensitive_paths_audit.txt', raw_content: { paths: [] } });
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
async function sendAllConfigFiles(configFiles, worktreeReport, hardwareUuid, authKey) {
|
|
231
|
+
const hookType = resolveHookTypeFromEnv();
|
|
232
|
+
const manifest = configFiles.map((c) => canonicalCursorUserStateVscdbPath(c.file_path));
|
|
233
|
+
const workspaceRepo = ensureWorkspaceRepoEnv(manifest);
|
|
234
|
+
const hookRequestId = await sendHookRequestCreate(hardwareUuid, authKey, hookType, workspaceRepo);
|
|
235
|
+
hookRunLog(`hook-request id=${hookRequestId ?? 'none'}`);
|
|
236
|
+
const ingestSessionId = await sendIngestSessionStart(hardwareUuid, authKey);
|
|
237
|
+
hookRunLog(`ingest-session id=${ingestSessionId ?? 'none'}`);
|
|
238
|
+
const estimatedRequests = Math.ceil(configFiles.length / BATCH_CHUNK_SIZE);
|
|
239
|
+
hookRunLog(`config send: batch ${configFiles.length} file(s) in ~${estimatedRequests} request(s)${hookRequestId != null ? ` hook_request_id=${hookRequestId}` : ''}${ingestSessionId ? ` ingest_session_id=${ingestSessionId}` : ''}`);
|
|
240
|
+
const batchResult = await sendConfigFilesBatch(configFiles, hardwareUuid, authKey, hookRequestId ?? undefined, ingestSessionId ?? undefined);
|
|
241
|
+
hookRunLog(`config files ${batchResult.accepted}/${configFiles.length} logged (batch)`);
|
|
242
|
+
if (batchResult.failed > 0)
|
|
243
|
+
hookRunLog(`config_failed: ${batchResult.failed} item(s) in batch`);
|
|
244
|
+
if (hookRequestId != null) {
|
|
245
|
+
const hookLogContent = readHookLog();
|
|
246
|
+
const ok = await sendHookRequestUpdateManifest(hardwareUuid, authKey, hookRequestId, manifest, hookLogContent || undefined);
|
|
247
|
+
hookRunLog(`hook-request update manifest result=${ok ? 'ok' : 'fail'}`);
|
|
248
|
+
}
|
|
249
|
+
// Finish ingest session: failed_uploads hold set, worktree prune, config prune-on-absence, and scans.
|
|
250
|
+
// Run finish whenever a session was started — even if every upload failed — so worktree
|
|
251
|
+
// cleanup still runs for machines with only stale worktrees on disk.
|
|
252
|
+
if (ingestSessionId) {
|
|
253
|
+
const ok = await sendIngestSessionFinish(hardwareUuid, authKey, ingestSessionId, batchResult.failedArtifacts, worktreeReport);
|
|
254
|
+
hookRunLog(`ingest-session finish result=${ok ? 'ok' : 'fail'}`
|
|
255
|
+
+ (batchResult.failedArtifacts.length > 0
|
|
256
|
+
? ` held=${batchResult.failedArtifacts.length} failed upload(s)`
|
|
257
|
+
: ''));
|
|
258
|
+
}
|
|
259
|
+
// Exit 0 if anything was persisted so the shell hook keeps last_log and throttles. Exit 1 only when
|
|
260
|
+
// every item failed (e.g. SQLite locked on server) — otherwise partial success used to return 1,
|
|
261
|
+
// the hook deleted last_log, and the next prompt re-uploaded all files every time.
|
|
262
|
+
const exitCode = batchResult.accepted > 0 ? 0 : 1;
|
|
263
|
+
const failedCount = configFiles.length - batchResult.accepted;
|
|
264
|
+
hookRunLog(`summary: exit=${exitCode} collected=${configFiles.length} sent=${batchResult.accepted} failed=${failedCount} hook_id=${hookRequestId ?? 'n/a'}`);
|
|
265
|
+
hookRunLog(`done`);
|
|
266
|
+
// Inventory telemetry — awaited so the POST lands before process.exit.
|
|
267
|
+
const outcome = exitCode === 0 ? (failedCount > 0 ? 'partial' : 'ok') : 'failed';
|
|
268
|
+
await reportFunctionalRun({
|
|
269
|
+
unit: 'inventory',
|
|
270
|
+
command: 'log_config_files',
|
|
271
|
+
outcome,
|
|
272
|
+
exit_code: exitCode,
|
|
273
|
+
message: `collected=${configFiles.length} sent=${batchResult.accepted} failed=${failedCount}`,
|
|
274
|
+
metadata: hookRequestId != null ? { hook_request_id: String(hookRequestId) } : undefined,
|
|
275
|
+
});
|
|
276
|
+
process.exit(exitCode);
|
|
277
|
+
}
|
|
278
|
+
async function main() {
|
|
279
|
+
hookLogReplace();
|
|
280
|
+
getStoryId();
|
|
281
|
+
void flushBufferedClientEvents();
|
|
282
|
+
const hardwareUuid = resolveHardwareUuid();
|
|
283
|
+
const endpointBase = loadEndpointBase();
|
|
284
|
+
hookRunLog(`start endpoint=${endpointBase} hardware_uuid=${hardwareUuid}`);
|
|
285
|
+
hookRunLog(`endpoint_source=${getEndpointSource()} cwd=${process.cwd()}`);
|
|
286
|
+
const envForLog = (v) => (v && v.trim() ? v.trim() : 'unset');
|
|
287
|
+
hookRunLog(`env: OPTIMUS_HOOK_TYPE=${envForLog(process.env.OPTIMUS_HOOK_TYPE)} OPTIMUS_PROJECT_DIR=${envForLog(process.env.OPTIMUS_PROJECT_DIR)} OPTIMUS_WORKSPACE_REPO=${envForLog(process.env.OPTIMUS_WORKSPACE_REPO)} OPTIMUS_ENDPOINT=${envForLog(process.env.OPTIMUS_ENDPOINT)}`);
|
|
288
|
+
let authKey;
|
|
289
|
+
try {
|
|
290
|
+
authKey = await ensureAuthentication(hardwareUuid);
|
|
291
|
+
}
|
|
292
|
+
catch (err) {
|
|
293
|
+
hookRunLog(`auth: failed ${err instanceof Error ? err.message : String(err)}`);
|
|
294
|
+
// Auth failure is its own functional unit; report (awaited) then exit so we don't
|
|
295
|
+
// also surface it as a generic inventory failure via the cli catch.
|
|
296
|
+
await reportFunctionalFailureAwait('auth_version', 'ensureAuthentication', err);
|
|
297
|
+
process.exit(1);
|
|
298
|
+
}
|
|
299
|
+
let configFiles;
|
|
300
|
+
let worktreeReport = [];
|
|
301
|
+
try {
|
|
302
|
+
const collected = await collectAllConfigFiles(endpointBase);
|
|
303
|
+
configFiles = collected.configFiles;
|
|
304
|
+
worktreeReport = collected.worktreeReport;
|
|
305
|
+
}
|
|
306
|
+
catch (err) {
|
|
307
|
+
hookRunLog(`patterns_error: ${err instanceof Error ? err.message : String(err)}`);
|
|
308
|
+
throw err;
|
|
309
|
+
}
|
|
310
|
+
await addSensitivePathsAudit(endpointBase, configFiles);
|
|
311
|
+
ensureCursorUserSettingsSnapshotInBatch(configFiles);
|
|
312
|
+
const fileTypes = [...new Set(configFiles.map((c) => c.file_type))].join(', ');
|
|
313
|
+
hookRunLog(`collected ${configFiles.length} config file(s) file_types=${fileTypes}`);
|
|
314
|
+
for (const c of configFiles) {
|
|
315
|
+
if (!c.file_path.replace(/\\/g, '/').includes('#composerState'))
|
|
316
|
+
continue;
|
|
317
|
+
const tail = c.file_path.length > 120 ? `…${c.file_path.slice(-120)}` : c.file_path;
|
|
318
|
+
hookRunLog(`diag pre-upload #composerState ${summarizeComposerPayloadForDiagnostics(c.raw_content)} path=${tail}`);
|
|
319
|
+
}
|
|
320
|
+
const claudeSettings = configFiles.filter((c) => c.file_type === 'claude_settings');
|
|
321
|
+
if (claudeSettings.length > 0)
|
|
322
|
+
hookRunLog(`claude_settings in batch: ${claudeSettings.length} path(s): ${claudeSettings.map((c) => c.file_path).join(', ')}`);
|
|
323
|
+
else
|
|
324
|
+
hookRunLog(`claude_settings in batch: 0 (none collected)`);
|
|
325
|
+
if (configFiles.length === 0) {
|
|
326
|
+
hookRunLog('no config files found, exiting');
|
|
327
|
+
process.exit(0);
|
|
328
|
+
}
|
|
329
|
+
await sendAllConfigFiles(configFiles, worktreeReport, hardwareUuid, authKey);
|
|
330
|
+
}
|
|
331
|
+
async function logSingleFile(filePath) {
|
|
332
|
+
const hardwareUuid = resolveHardwareUuid();
|
|
333
|
+
const authKey = await ensureAuthentication(hardwareUuid);
|
|
334
|
+
const patternsResponse = await getFileCollectionPatterns(loadEndpointBase());
|
|
335
|
+
const isVscdb = isVscdbVirtualPath(filePath, patternsResponse ?? null);
|
|
336
|
+
if (!existsSync(filePath) && !isVscdb) {
|
|
337
|
+
console.error(`File not found: ${filePath}`);
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
const patterns = patternsResponse?.patterns ?? [];
|
|
341
|
+
let rawContent = null;
|
|
342
|
+
let fileType = determineFileTypeFromPath(filePath, patterns) ?? undefined;
|
|
343
|
+
if (isVscdb) {
|
|
344
|
+
const vscdbResult = tryReadVscdbVirtualFile(filePath, patternsResponse ?? null);
|
|
345
|
+
if (!vscdbResult) {
|
|
346
|
+
console.error(`Could not read vscdb virtual file: ${filePath}`);
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
rawContent = vscdbResult.rawContent;
|
|
350
|
+
fileType = vscdbResult.fileType;
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
rawContent = readJSONFile(filePath);
|
|
354
|
+
if (!rawContent) {
|
|
355
|
+
const markdown = readMarkdownFile(filePath);
|
|
356
|
+
if (markdown !== null) {
|
|
357
|
+
rawContent = { content: markdown, source: 'file' };
|
|
358
|
+
if (!fileType)
|
|
359
|
+
fileType = determineFileTypeFromPath(filePath, patterns) ?? undefined;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
if (!rawContent) {
|
|
364
|
+
console.error(`Could not read file: ${filePath}`);
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
if (!fileType) {
|
|
368
|
+
fileType = determineFileTypeFromPath(filePath, patterns) ?? undefined;
|
|
369
|
+
if (!fileType) {
|
|
370
|
+
console.error(`Could not determine file type for: ${filePath}`);
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return sendConfigFile({ file_type: fileType, file_path: filePath, raw_content: rawContent }, hardwareUuid, authKey);
|
|
375
|
+
}
|
|
376
|
+
export { main, logSingleFile, createSignature, canonicalizePayload, sendConfigFile, sendConfigFilesBatch, BATCH_CHUNK_SIZE };
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Paths and JSON persistence for ~/.optimuslabs/management:
|
|
3
|
+
* - remediation_instructions.json (remediations[])
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { dirname, join } from 'node:path';
|
|
7
|
+
import { homedir } from 'node:os';
|
|
8
|
+
import { OPTIMUSLABS_MANAGEMENT_REL, OPTIMUSLABS_LOGS_REL } from '../../bootstrap_constants.js';
|
|
9
|
+
export const REMEDIATION_INSTRUCTIONS_BASENAME = 'remediation_instructions.json';
|
|
10
|
+
/** Pending state.vscdb writes applied after Cursor exits (IDE holds the DB locked). */
|
|
11
|
+
export const DEFERRED_VSCDB_APPLY_BASENAME = 'optimus_deferred_vscdb_apply.json';
|
|
12
|
+
/**
|
|
13
|
+
* Shell stdout/stderr from the trusted deferred restart pipeline (apply_deferred_vscdb + open Cursor).
|
|
14
|
+
* Co-located in the root vscdb/ folder next to the apply-queue + contract it operates on — the whole
|
|
15
|
+
* deferred-vscdb-restart subsystem lives together, so there is no separate logs/vscdb/.
|
|
16
|
+
*/
|
|
17
|
+
export const DEFERRED_VSCDB_RESTART_LOG_BASENAME = 'deferred_vscdb_restart.log';
|
|
18
|
+
/**
|
|
19
|
+
* Cached subset of GET file-patterns: reactive ItemTable key + composer shadow keys from the backend.
|
|
20
|
+
* Written when patterns are fetched; remediations and vscdb reads use this so the npm package stays free
|
|
21
|
+
* of hardcoded Cursor paths.
|
|
22
|
+
*/
|
|
23
|
+
export const FILE_COLLECTION_VSCDB_CONTRACT_BASENAME = 'file_collection_vscdb_contract.json';
|
|
24
|
+
/** User preferences for Optimus macOS dialogs (hook-driven). */
|
|
25
|
+
export const DIALOG_PREFERENCES_BASENAME = 'optimus_dialog_preferences.json';
|
|
26
|
+
export function sanitizeReactiveStorageItemKey(raw) {
|
|
27
|
+
if (typeof raw !== 'string')
|
|
28
|
+
return undefined;
|
|
29
|
+
const trimmed = raw.trim();
|
|
30
|
+
if (!trimmed || trimmed.length > 512)
|
|
31
|
+
return undefined;
|
|
32
|
+
if (/['"\\]/.test(trimmed))
|
|
33
|
+
return undefined;
|
|
34
|
+
return trimmed;
|
|
35
|
+
}
|
|
36
|
+
function sanitizeComposerShadowKeys(raw) {
|
|
37
|
+
if (!Array.isArray(raw))
|
|
38
|
+
return [];
|
|
39
|
+
return raw
|
|
40
|
+
.filter((entry) => typeof entry === 'string')
|
|
41
|
+
.map((entry) => entry.trim())
|
|
42
|
+
.filter((entry) => entry.length > 0);
|
|
43
|
+
}
|
|
44
|
+
export function sanitizeFileCollectionVscdbContract(contract) {
|
|
45
|
+
if (!contract || contract.version !== 1)
|
|
46
|
+
return null;
|
|
47
|
+
return {
|
|
48
|
+
version: 1,
|
|
49
|
+
reactive_storage_item_key: sanitizeReactiveStorageItemKey(contract.reactive_storage_item_key),
|
|
50
|
+
composer_shadow_keys: sanitizeComposerShadowKeys(contract.composer_shadow_keys),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export function getManagementDir() {
|
|
54
|
+
return join(homedir(), OPTIMUSLABS_MANAGEMENT_REL);
|
|
55
|
+
}
|
|
56
|
+
/** Root of the grouped hook log subtree (~/.optimuslabs/management/logs). */
|
|
57
|
+
export function getLogsDir() {
|
|
58
|
+
return join(homedir(), OPTIMUSLABS_LOGS_REL);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Directory for vscdb state/contract files (~/.optimuslabs/management/vscdb), keeping the
|
|
62
|
+
* several generated vscdb artifacts grouped instead of dangling at the management root.
|
|
63
|
+
* Includes deferred_vscdb_restart.log (detached restart pipeline output).
|
|
64
|
+
*/
|
|
65
|
+
export function getVscdbStateDir() {
|
|
66
|
+
return join(getManagementDir(), 'vscdb');
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Directory for non-log remediation state (~/.optimuslabs/management/remediation):
|
|
70
|
+
* remediation_instructions.json (what to fix) and remediation_apply_tracking.json
|
|
71
|
+
* (quarantine + verify-failure history that escapes the autofix restart loop).
|
|
72
|
+
* Remediation session logs live under logs/compliance/.
|
|
73
|
+
*/
|
|
74
|
+
export function getRemediationStateDir() {
|
|
75
|
+
return join(getManagementDir(), 'remediation');
|
|
76
|
+
}
|
|
77
|
+
export function getRemediationInstructionsPath() {
|
|
78
|
+
return join(getRemediationStateDir(), REMEDIATION_INSTRUCTIONS_BASENAME);
|
|
79
|
+
}
|
|
80
|
+
export function getDeferredVscdbApplyPath() {
|
|
81
|
+
return join(getVscdbStateDir(), DEFERRED_VSCDB_APPLY_BASENAME);
|
|
82
|
+
}
|
|
83
|
+
export function getDeferredVscdbRestartLogPath() {
|
|
84
|
+
return join(getVscdbStateDir(), DEFERRED_VSCDB_RESTART_LOG_BASENAME);
|
|
85
|
+
}
|
|
86
|
+
export function getFileCollectionVscdbContractPath() {
|
|
87
|
+
return join(getVscdbStateDir(), FILE_COLLECTION_VSCDB_CONTRACT_BASENAME);
|
|
88
|
+
}
|
|
89
|
+
/** Directory for macOS dialog assets/state (~/.optimuslabs/management/dialog): prefs + cached icon. */
|
|
90
|
+
export function getDialogStateDir() {
|
|
91
|
+
return join(getManagementDir(), 'dialog');
|
|
92
|
+
}
|
|
93
|
+
export function getDialogPreferencesPath() {
|
|
94
|
+
return join(getDialogStateDir(), DIALOG_PREFERENCES_BASENAME);
|
|
95
|
+
}
|
|
96
|
+
export function readFileCollectionVscdbContract() {
|
|
97
|
+
const path = getFileCollectionVscdbContractPath();
|
|
98
|
+
if (!existsSync(path))
|
|
99
|
+
return null;
|
|
100
|
+
try {
|
|
101
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
102
|
+
return sanitizeFileCollectionVscdbContract(parsed);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export function writeFileCollectionVscdbContract(contract) {
|
|
109
|
+
const sanitized = sanitizeFileCollectionVscdbContract(contract);
|
|
110
|
+
if (!sanitized)
|
|
111
|
+
return;
|
|
112
|
+
atomicWriteJson(getFileCollectionVscdbContractPath(), sanitized);
|
|
113
|
+
}
|
|
114
|
+
export function atomicWriteJson(filePath, data) {
|
|
115
|
+
const dir = dirname(filePath);
|
|
116
|
+
if (!existsSync(dir))
|
|
117
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
118
|
+
const tmp = `${filePath}.tmp`;
|
|
119
|
+
writeFileSync(tmp, JSON.stringify(data, null, 2), 'utf8');
|
|
120
|
+
renameSync(tmp, filePath);
|
|
121
|
+
}
|
|
122
|
+
function parseInstructionsRaw(raw) {
|
|
123
|
+
try {
|
|
124
|
+
const parsed = JSON.parse(raw);
|
|
125
|
+
return Array.isArray(parsed.remediations) ? parsed : { remediations: [] };
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return { remediations: [] };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
export function writeRemediationInstructionsFile(file) {
|
|
132
|
+
atomicWriteJson(getRemediationInstructionsPath(), file);
|
|
133
|
+
}
|
|
134
|
+
export function readRemediationInstructionsFile() {
|
|
135
|
+
const path = getRemediationInstructionsPath();
|
|
136
|
+
if (!existsSync(path))
|
|
137
|
+
return { remediations: [] };
|
|
138
|
+
try {
|
|
139
|
+
return parseInstructionsRaw(readFileSync(path, 'utf8'));
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return { remediations: [] };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map ops.add/set/remove keys to JSON dot-paths for apply + verify.
|
|
3
|
+
*
|
|
4
|
+
* Server may use either the leaf key (e.g. `deny`) or the full setting_path
|
|
5
|
+
* (e.g. `cursor.general.globalCursorIgnoreList`) as the op map key.
|
|
6
|
+
*/
|
|
7
|
+
export function resolveOpsTargetPath(settingPath, opKey) {
|
|
8
|
+
const parts = settingPath.split('.');
|
|
9
|
+
const leafKey = parts[parts.length - 1] ?? '';
|
|
10
|
+
const parentPath = parts.slice(0, -1).join('.');
|
|
11
|
+
if (opKey === settingPath || opKey === leafKey) {
|
|
12
|
+
return settingPath;
|
|
13
|
+
}
|
|
14
|
+
if (opKey.includes('.')) {
|
|
15
|
+
return opKey;
|
|
16
|
+
}
|
|
17
|
+
return parentPath ? `${parentPath}.${opKey}` : opKey;
|
|
18
|
+
}
|