rea-agents 2.0.0 → 2.2.0

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 (273) hide show
  1. package/README.md +103 -38
  2. package/bridge/ghidra/ReaGhidraBridge.java +96 -11
  3. package/dist/application/AndroidApplicationService.js +17 -0
  4. package/dist/application/AppleApplicationService.js +17 -0
  5. package/dist/application/ArtifactExtraction.js +1 -1
  6. package/dist/application/ArtifactInventory/classify.js +42 -0
  7. package/dist/application/ArtifactInventory/hash.js +43 -0
  8. package/dist/application/ArtifactInventory/reader.js +41 -0
  9. package/dist/application/ArtifactInventory/scanCanonical.js +155 -0
  10. package/dist/application/ArtifactInventory/scanReader.js +124 -0
  11. package/dist/application/ArtifactInventory/types.js +10 -0
  12. package/dist/application/ArtifactInventory.js +9 -351
  13. package/dist/application/AuthorizedArtifactInventory.js +2 -2
  14. package/dist/application/BinarySession.js +47 -270
  15. package/dist/application/BinarySessionExecution.js +28 -0
  16. package/dist/application/BinarySessionOpen.js +39 -0
  17. package/dist/application/BinarySessionRecords.js +149 -0
  18. package/dist/application/BinarySessionStatus.js +75 -0
  19. package/dist/application/CapabilityInventory.js +24 -2
  20. package/dist/application/ClientRegistrationStatus.js +11 -4
  21. package/dist/application/CrossVersionInventory.js +8 -2
  22. package/dist/application/CrossVersionInvestigation.js +1 -23
  23. package/dist/application/CrossVersionInvestigationCollections.js +23 -0
  24. package/dist/application/DirectAnalysis.js +47 -44
  25. package/dist/application/Doctor.js +156 -238
  26. package/dist/application/DoctorDiagnostics.js +180 -0
  27. package/dist/application/ElectronBoundaryAnalysis.js +23 -7
  28. package/dist/application/ElectronBoundaryGraphWindows.js +32 -8
  29. package/dist/application/EnhancedToolPagination.js +22 -0
  30. package/dist/application/EnhancedToolTypes.js +4 -0
  31. package/dist/application/EnhancedTools.js +20 -37
  32. package/dist/application/InstrumentedJavaScriptReplayHost.js +36 -0
  33. package/dist/application/InventoryProjectionEvidence.js +41 -0
  34. package/dist/application/InvestigationProviders.js +12 -0
  35. package/dist/application/InvestigationWorkspaceStore.js +24 -10
  36. package/dist/application/JavaScriptArtifactAnalysis.js +45 -6
  37. package/dist/application/JavaScriptArtifactFiles.js +6 -2
  38. package/dist/application/JavaScriptArtifactGraphBuilder.js +23 -1
  39. package/dist/application/JavaScriptArtifactGraphBundlers.js +225 -0
  40. package/dist/application/JavaScriptArtifactGraphContext.js +37 -36
  41. package/dist/application/JavaScriptArtifactGraphDocuments.js +27 -4
  42. package/dist/application/JavaScriptArtifactGraphFindings.js +54 -8
  43. package/dist/application/JavaScriptArtifactGraphStructure.js +29 -101
  44. package/dist/application/JavaScriptArtifactPathResolution.js +269 -0
  45. package/dist/application/JavaScriptArtifactReconstruction.js +1 -1
  46. package/dist/application/JavaScriptJsonModules.js +39 -0
  47. package/dist/application/JavaScriptModuleRelationships.js +262 -0
  48. package/dist/application/JavaScriptReplayPermission.js +16 -0
  49. package/dist/application/JavaScriptReplayService.js +182 -156
  50. package/dist/application/LinuxHopper.js +10 -0
  51. package/dist/application/MacHopper.js +22 -11
  52. package/dist/application/NodeRuntimeCharacterizationService.js +196 -0
  53. package/dist/application/PermissionAuthority.js +106 -63
  54. package/dist/application/ProcessCaptureCapability.js +1 -1
  55. package/dist/application/ProcessCapturePermission.js +13 -0
  56. package/dist/application/ProcessCli.js +2 -12
  57. package/dist/application/ProcessHarness.js +1 -1
  58. package/dist/application/ProcessPairedExperiment.js +72 -0
  59. package/dist/application/ProjectPermissionStore.js +48 -9
  60. package/dist/application/RealHopperAssertions.js +76 -0
  61. package/dist/application/ReconstructionCoverageService.js +69 -0
  62. package/dist/application/ReconstructionCoverageWorkspaceStore.js +31 -0
  63. package/dist/application/RuntimeExecutableDiagnostics.js +172 -0
  64. package/dist/application/Setup.js +148 -260
  65. package/dist/application/SetupClientConfiguration.js +224 -0
  66. package/dist/application/SetupClients.js +24 -2
  67. package/dist/application/SetupPlan.js +124 -12
  68. package/dist/application/SetupSkill.js +54 -36
  69. package/dist/application/SupportedClients.js +1 -0
  70. package/dist/application/Uninstall.js +12 -3
  71. package/dist/application/Upgrade.js +16 -1
  72. package/dist/application/WindowsCapabilities.js +86 -0
  73. package/dist/artifacts/AsarArtifactReader.js +11 -0
  74. package/dist/browser/BrowserProcessStartup.js +88 -0
  75. package/dist/browser/CdpCaptureDocuments.js +12 -8
  76. package/dist/browser/CdpCaptureEventBodyShapes.js +75 -0
  77. package/dist/browser/CdpCaptureEventHandlers.js +336 -0
  78. package/dist/browser/CdpCaptureEventHelpers.js +71 -0
  79. package/dist/browser/CdpCaptureEventState.js +1 -0
  80. package/dist/browser/CdpCaptureEventTypes.js +1 -0
  81. package/dist/browser/CdpCaptureEvents.js +29 -496
  82. package/dist/browser/CdpObservationSession.js +51 -8
  83. package/dist/browser/CdpPageCapture.js +12 -133
  84. package/dist/browser/CdpPageCaptureScripts.js +97 -0
  85. package/dist/browser/CdpPageCaptureWorkers.js +46 -0
  86. package/dist/browser/CdpWebMcpDiscovery.js +38 -14
  87. package/dist/browser/PngVisualDiff.js +9 -2
  88. package/dist/catalogIdentity.js +4 -0
  89. package/dist/cli/artifactCommands.js +82 -0
  90. package/dist/cli/coreAnalysisCommands.js +232 -0
  91. package/dist/cli/managedCommands.js +397 -0
  92. package/dist/cli/managedProjectGraphCommand.js +24 -0
  93. package/dist/cli/options.js +9 -0
  94. package/dist/cli/setupCommands.js +113 -0
  95. package/dist/cli/types.js +1 -0
  96. package/dist/cli/utilityCommands.js +122 -0
  97. package/dist/cli.js +10 -768
  98. package/dist/cliApplicationCommands.js +118 -4
  99. package/dist/cliBrowserAdvancedCommands.js +73 -16
  100. package/dist/cliBrowserCommands.js +39 -115
  101. package/dist/cliCommandNames.js +4 -0
  102. package/dist/cliElectronCommands.js +33 -62
  103. package/dist/cliEvidenceCommands.js +13 -2
  104. package/dist/cliInvestigationCommands.js +53 -10
  105. package/dist/cliObservationOptions.js +137 -0
  106. package/dist/cliPolicyCommands.js +80 -46
  107. package/dist/cliSetup.js +306 -0
  108. package/dist/config/environment.js +107 -0
  109. package/dist/config/parseConfig.js +228 -0
  110. package/dist/config/parsers.js +68 -0
  111. package/dist/config/permissions.js +30 -0
  112. package/dist/config/types.js +1 -0
  113. package/dist/config.js +1 -375
  114. package/dist/contracts/applicationToolContracts.js +134 -0
  115. package/dist/contracts/electronToolContracts.js +51 -3
  116. package/dist/contracts/managedToolContracts.js +8 -3
  117. package/dist/contracts/managedWorkflowToolContracts.js +3 -3
  118. package/dist/contracts/providerSelection.js +2 -0
  119. package/dist/contracts/sessionStatusContract.js +26 -0
  120. package/dist/contracts/toolContractTypes.js +12 -1
  121. package/dist/contracts/toolContracts.js +5 -12
  122. package/dist/contracts/toolEffects.js +14 -0
  123. package/dist/contracts/toolOutputSchemaGroups.js +274 -0
  124. package/dist/contracts/toolOutputSchemaPrimitives.js +328 -0
  125. package/dist/contracts/toolOutputSchemas.js +2 -550
  126. package/dist/domain/analysisErrorPresentation.js +234 -0
  127. package/dist/domain/analysisErrorProjection.js +284 -0
  128. package/dist/domain/androidApplication.js +206 -0
  129. package/dist/domain/appleApplication.js +221 -0
  130. package/dist/domain/artifactComparison.js +34 -15
  131. package/dist/domain/binaryTarget.js +87 -37
  132. package/dist/domain/boundedCartesianProjection.js +15 -0
  133. package/dist/domain/browserObservation.js +1 -285
  134. package/dist/domain/browserObservationSchemas.js +286 -0
  135. package/dist/domain/electronStaticAnalysisBrowser.js +14 -2
  136. package/dist/domain/errors.js +4 -486
  137. package/dist/domain/functionComparison.js +4 -165
  138. package/dist/domain/functionComparisonDimensions.js +164 -0
  139. package/dist/domain/javascriptApplicationVersionItems.js +18 -6
  140. package/dist/domain/javascriptApplicationVersionKeys.js +8 -8
  141. package/dist/domain/javascriptExportInstrumentation.js +97 -0
  142. package/dist/domain/javascriptFeatureTraversal.js +19 -7
  143. package/dist/domain/javascriptRuntimeStaticCandidates.js +3 -1
  144. package/dist/domain/javascriptSemanticAnalysis.js +386 -0
  145. package/dist/domain/javascriptSemanticIr.js +30 -0
  146. package/dist/domain/javascriptSemanticProjection.js +366 -0
  147. package/dist/domain/javascriptSemanticState.js +40 -0
  148. package/dist/domain/javascriptSemanticTraversal.js +22 -0
  149. package/dist/domain/javascriptSemanticValues.js +352 -0
  150. package/dist/domain/javascriptStaticAnalysis.js +5 -221
  151. package/dist/domain/javascriptStaticAnalysisBundler.js +209 -0
  152. package/dist/domain/javascriptStaticAnalysisCalls.js +180 -0
  153. package/dist/domain/javascriptStaticAnalysisHelpers.js +71 -2
  154. package/dist/domain/knownPageCoverage.js +21 -0
  155. package/dist/domain/managedApplicationGraph.js +14 -364
  156. package/dist/domain/managedApplicationGraphCoverage.js +170 -0
  157. package/dist/domain/managedApplicationGraphNodeCoverage.js +6 -0
  158. package/dist/domain/managedApplicationGraphNodes.js +336 -0
  159. package/dist/domain/managedArtifact.js +16 -8
  160. package/dist/domain/managedMemberComparison.js +37 -391
  161. package/dist/domain/managedMemberComparisonCoverage.js +72 -0
  162. package/dist/domain/managedMemberComparisonMatch.js +356 -0
  163. package/dist/domain/managedNativeVerification.js +27 -389
  164. package/dist/domain/managedNativeVerificationMatch.js +272 -0
  165. package/dist/domain/managedNativeVerificationSchemas.js +141 -0
  166. package/dist/domain/managedReconstruction.js +82 -69
  167. package/dist/domain/managedRuntimeCorrelation.js +12 -3
  168. package/dist/domain/nodeRuntimeCharacterization.js +66 -0
  169. package/dist/domain/permissionPolicy.js +2 -0
  170. package/dist/domain/processComparison.js +19 -0
  171. package/dist/domain/processPairedExperiment.js +128 -0
  172. package/dist/domain/reconstructionCoverage.js +387 -0
  173. package/dist/domain/reconstructionCoverageEvaluation.js +212 -0
  174. package/dist/domain/reconstructionVerificationSchemas.js +8 -0
  175. package/dist/domain/replayMachine.js +316 -0
  176. package/dist/domain/replayMachineRuntime.js +215 -0
  177. package/dist/domain/runtimeCharacterization.js +85 -0
  178. package/dist/domain/runtimeIdentification.js +210 -0
  179. package/dist/domain/webBundleAnalyzer.js +3 -409
  180. package/dist/domain/webBundleAnalyzerAst.js +77 -0
  181. package/dist/domain/webBundleAnalyzerInspection.js +307 -0
  182. package/dist/domain/webBundleAnalyzerResult.js +120 -0
  183. package/dist/dotnet/ManagedArtifactInspector.js +50 -29
  184. package/dist/dotnet/ManagedMemberInspector.js +51 -785
  185. package/dist/dotnet/ManagedMemberInspectorCore.js +297 -0
  186. package/dist/dotnet/ManagedMemberInstructionDecoder.js +427 -0
  187. package/dist/dotnet/ManagedMemberRows.js +120 -0
  188. package/dist/dotnet/ManagedMetadataInventory.js +42 -306
  189. package/dist/dotnet/ManagedMetadataInventoryRows.js +292 -0
  190. package/dist/dotnet/ManagedMetadataLayout.js +48 -111
  191. package/dist/dotnet/ManagedMetadataRowSizes.js +54 -0
  192. package/dist/dotnet/ManagedMethodBodyReader.js +143 -0
  193. package/dist/dotnet/ManagedNativeBoundaryHelpers.js +311 -0
  194. package/dist/dotnet/ManagedNativeBoundaryInspector.js +102 -350
  195. package/dist/dotnet/ManagedPeReader.js +59 -28
  196. package/dist/evaluation/CodexAgentEval.js +132 -0
  197. package/dist/generatedPackageMetadata.js +2 -2
  198. package/dist/ghidra/GhidraClient.js +76 -91
  199. package/dist/ghidra/GhidraClientStartup.js +29 -0
  200. package/dist/ghidra/GhidraClientWire.js +34 -0
  201. package/dist/ghidra/GhidraDefaults.js +1 -1
  202. package/dist/ghidra/GhidraDiagnostics.js +6 -1
  203. package/dist/ghidra/GhidraInstallation.js +20 -15
  204. package/dist/ghidra/GhidraLauncher.js +93 -29
  205. package/dist/ghidra/GhidraProvider.js +61 -161
  206. package/dist/ghidra/GhidraProviderCapabilities.js +173 -0
  207. package/dist/ghidra/GhidraSessionValues.js +2 -0
  208. package/dist/ghidra/GhidraSocketConnection.js +44 -7
  209. package/dist/ghidra/GhidraTargetSnapshot.js +30 -0
  210. package/dist/ghidra/GhidraTransport.js +71 -0
  211. package/dist/identity.js +4 -2
  212. package/dist/main/messages.js +5 -0
  213. package/dist/main/reload.js +49 -0
  214. package/dist/main/shutdown.js +25 -0
  215. package/dist/main/startup.js +14 -0
  216. package/dist/main/state.js +26 -0
  217. package/dist/main/transport.js +61 -0
  218. package/dist/main/types.js +1 -0
  219. package/dist/main.js +35 -146
  220. package/dist/process/PrivateRuntimeRoot.js +3 -2
  221. package/dist/process/ProcessOwnership.js +35 -0
  222. package/dist/process/ProviderProcess.js +2 -0
  223. package/dist/replay/JavaScriptReplayWorker.js +64 -50
  224. package/dist/replay/LinuxJavaScriptReplayRunner.js +173 -390
  225. package/dist/replay/LinuxJavaScriptReplaySandbox.js +140 -0
  226. package/dist/replay/ReplayOutcome.js +95 -0
  227. package/dist/replay/ReplayProcessLifecycle.js +71 -0
  228. package/dist/server/ProcessCaptureElicitation.js +157 -0
  229. package/dist/server/createServer.js +124 -56
  230. package/dist/server/javascriptApplicationResult.js +85 -0
  231. package/dist/server/registerApplicationTools/characterization.js +57 -0
  232. package/dist/server/registerApplicationTools/compareVersions.js +34 -0
  233. package/dist/server/registerApplicationTools/controlledReplay.js +38 -0
  234. package/dist/server/registerApplicationTools/coverage.js +84 -0
  235. package/dist/server/registerApplicationTools/helpers.js +42 -0
  236. package/dist/server/registerApplicationTools/traceFeature.js +26 -0
  237. package/dist/server/registerApplicationTools/types.js +1 -0
  238. package/dist/server/registerApplicationTools.js +10 -112
  239. package/dist/server/registerBrowserTools.js +59 -83
  240. package/dist/server/registerElectronTools.js +48 -49
  241. package/dist/server/registerEnhancedTools.js +34 -42
  242. package/dist/server/registerEvidenceResources.js +56 -0
  243. package/dist/server/registerInvestigationTools/helpers.js +74 -0
  244. package/dist/server/registerInvestigationTools/types.js +1 -0
  245. package/dist/server/registerInvestigationTools.js +83 -116
  246. package/dist/server/registerJavaScriptApplicationGraphResource.js +69 -0
  247. package/dist/server/registerManagedTools.js +18 -1
  248. package/dist/server/registerManagedWorkflowTools/compareManagedMembers.js +65 -0
  249. package/dist/server/registerManagedWorkflowTools/contract.js +8 -0
  250. package/dist/server/registerManagedWorkflowTools/evidence.js +43 -0
  251. package/dist/server/registerManagedWorkflowTools/importManagedReconstruction.js +39 -0
  252. package/dist/server/registerManagedWorkflowTools/planManagedRuntimeCorrelation.js +39 -0
  253. package/dist/server/registerManagedWorkflowTools/projectManagedApplicationGraph.js +52 -0
  254. package/dist/server/registerManagedWorkflowTools/types.js +1 -0
  255. package/dist/server/registerManagedWorkflowTools/verifyManagedNativeBoundaries.js +75 -0
  256. package/dist/server/registerManagedWorkflowTools.js +10 -274
  257. package/dist/server/registerOfficialTools.js +73 -59
  258. package/dist/server/registerSessionRecordTools.js +137 -0
  259. package/dist/server/registerSessionStatusTool.js +97 -26
  260. package/dist/server/registerSessionTools.js +60 -164
  261. package/dist/server/sessionAvailabilityPolicy.js +10 -0
  262. package/dist/server/toolRegistrationOptions.js +101 -3
  263. package/dist/server/toolResult.js +15 -4
  264. package/package.json +11 -11
  265. package/skills/reverse-engineer-anything/SKILL.md +76 -0
  266. package/skills/reverse-engineer-anything/references/controlled-replay.md +12 -0
  267. package/skills/reverse-engineer-anything/references/evidence-workflows.md +33 -0
  268. package/skills/reverse-engineer-anything/references/javascript-applications.md +25 -0
  269. package/skills/reverse-engineer-anything/references/native-and-artifacts.md +36 -0
  270. package/skills/reverse-engineer-anything/references/runtime-observation.md +24 -0
  271. package/scripts/prepare-node-pty.mjs +0 -35
  272. package/scripts/rebuild-native.mjs +0 -22
  273. package/skills/rea-analysis/SKILL.md +0 -222
@@ -0,0 +1,224 @@
1
+ import { constants as fsConstants } from "node:fs";
2
+ import { copyFile, mkdir, readFile, rm, writeFile } from "node:fs/promises";
3
+ import { dirname } from "node:path";
4
+ import { z } from "zod";
5
+ import writeFileAtomic from "write-file-atomic";
6
+ import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
7
+ import { PRODUCT_IDENTITY } from "../identity.js";
8
+ import { resolveClientConfigTransactionPath } from "./ClientConfigPath.js";
9
+ const defaultCommand = () => [
10
+ "npx",
11
+ "-y",
12
+ PRODUCT_IDENTITY.registrationPackageSpecifier,
13
+ "mcp",
14
+ ];
15
+ const CODEX_STARTUP_TIMEOUT_SECONDS = 30;
16
+ /** Back up, atomically update, and semantically read back one JSON MCP configuration. */
17
+ export const configureJsonClient = async (client, environment = {}, command = defaultCommand()) => {
18
+ const transactionPath = await resolveClientConfigTransactionPath(client.configPath);
19
+ if (transactionPath === undefined)
20
+ return { status: "failed", reason: "path" };
21
+ let document = {};
22
+ let original;
23
+ try {
24
+ original = await readFile(transactionPath, "utf8");
25
+ document = parseObject(original);
26
+ }
27
+ catch (cause) {
28
+ if (!isMissing(cause))
29
+ return { status: "failed", reason: "readback" };
30
+ }
31
+ let servers;
32
+ try {
33
+ servers = parseOptionalObject(document.mcpServers);
34
+ }
35
+ catch {
36
+ return { status: "failed", reason: "readback" };
37
+ }
38
+ const desired = clientConfigurationDesired(client, normalizeProviderEnvironment(environment), command);
39
+ if (sameConfiguration(servers[PRODUCT_IDENTITY.mcpServerKey], desired))
40
+ return { status: "unchanged" };
41
+ let backupPath;
42
+ if (original !== undefined) {
43
+ backupPath = `${client.configPath}.rea.backup`;
44
+ if (!(await preserveConfigBackup(transactionPath, backupPath)))
45
+ return { status: "failed", reason: "backup" };
46
+ }
47
+ document.mcpServers = {
48
+ ...servers,
49
+ [PRODUCT_IDENTITY.mcpServerKey]: desired,
50
+ };
51
+ try {
52
+ await mkdir(dirname(client.configPath), { recursive: true });
53
+ await writeFileAtomic(transactionPath, `${JSON.stringify(document, null, 2)}\n`, {
54
+ encoding: "utf8",
55
+ mode: 0o600,
56
+ });
57
+ }
58
+ catch {
59
+ return { status: "failed", reason: "write" };
60
+ }
61
+ try {
62
+ const readback = parseObject(await readFile(transactionPath, "utf8"));
63
+ if (!sameConfiguration(parseOptionalObject(readback.mcpServers)[PRODUCT_IDENTITY.mcpServerKey], desired)) {
64
+ await restoreConfig(transactionPath, original);
65
+ return { status: "failed", reason: "readback" };
66
+ }
67
+ }
68
+ catch {
69
+ await restoreConfig(transactionPath, original);
70
+ return { status: "failed", reason: "readback" };
71
+ }
72
+ return {
73
+ status: "configured",
74
+ ...(backupPath === undefined ? {} : { backupPath }),
75
+ };
76
+ };
77
+ /** Back up, atomically update, and semantically read back Codex TOML configuration. */
78
+ export const configureTomlClient = async (client, environment = {}, command = defaultCommand()) => {
79
+ const transactionPath = await resolveClientConfigTransactionPath(client.configPath);
80
+ if (transactionPath === undefined)
81
+ return { status: "failed", reason: "path" };
82
+ let document = {};
83
+ let original;
84
+ try {
85
+ original = await readFile(transactionPath, "utf8");
86
+ document = objectSchema.parse(parseToml(original));
87
+ }
88
+ catch (cause) {
89
+ if (!isMissing(cause))
90
+ return { status: "failed", reason: "readback" };
91
+ }
92
+ let servers;
93
+ try {
94
+ servers = parseOptionalObject(document.mcp_servers);
95
+ }
96
+ catch {
97
+ return { status: "failed", reason: "readback" };
98
+ }
99
+ const desired = clientConfigurationDesired(client, normalizeProviderEnvironment(environment), command);
100
+ if (sameConfiguration(servers[PRODUCT_IDENTITY.mcpServerKey], desired))
101
+ return { status: "unchanged" };
102
+ const backupPath = original === undefined ? undefined : `${client.configPath}.rea.backup`;
103
+ if (backupPath !== undefined &&
104
+ !(await preserveConfigBackup(transactionPath, backupPath)))
105
+ return { status: "failed", reason: "backup" };
106
+ document.mcp_servers = {
107
+ ...servers,
108
+ [PRODUCT_IDENTITY.mcpServerKey]: desired,
109
+ };
110
+ try {
111
+ await mkdir(dirname(client.configPath), { recursive: true });
112
+ await writeFileAtomic(transactionPath, stringifyToml(document), {
113
+ encoding: "utf8",
114
+ mode: 0o600,
115
+ });
116
+ const readback = objectSchema.parse(parseToml(await readFile(transactionPath, "utf8")));
117
+ if (!sameConfiguration(parseOptionalObject(readback.mcp_servers)[PRODUCT_IDENTITY.mcpServerKey], desired))
118
+ throw new Error("TOML readback mismatch");
119
+ }
120
+ catch {
121
+ await restoreConfig(transactionPath, original);
122
+ return { status: "failed", reason: "readback" };
123
+ }
124
+ return {
125
+ status: "configured",
126
+ ...(backupPath === undefined ? {} : { backupPath }),
127
+ };
128
+ };
129
+ /** Determine whether an existing client configuration matches the desired registration. */
130
+ export const clientConfigurationAligned = async (client, providerEnvironment, command) => {
131
+ const desired = clientConfigurationDesired(client, providerEnvironment, command);
132
+ try {
133
+ const original = await readFile(client.configPath, "utf8");
134
+ const document = client.format === "toml"
135
+ ? objectSchema.parse(parseToml(original))
136
+ : parseObject(original);
137
+ const servers = parseOptionalObject(client.format === "toml" ? document.mcp_servers : document.mcpServers);
138
+ return sameConfiguration(servers[PRODUCT_IDENTITY.mcpServerKey], desired);
139
+ }
140
+ catch {
141
+ return false;
142
+ }
143
+ };
144
+ /** Preflight one client configuration before setup presents any mutations. */
145
+ export const inspectClientConfiguration = async (client, providerEnvironment, command) => {
146
+ if (client.format === "unsupported")
147
+ return { status: "already_current" };
148
+ const transactionPath = await resolveClientConfigTransactionPath(client.configPath);
149
+ if (transactionPath === undefined)
150
+ return {
151
+ status: "invalid",
152
+ remediation: "The configuration path is unsafe or unresolved. Check ownership and symbolic links before rerunning setup.",
153
+ };
154
+ let original;
155
+ try {
156
+ original = await readFile(transactionPath, "utf8");
157
+ }
158
+ catch (cause) {
159
+ if (isMissing(cause))
160
+ return { status: "create" };
161
+ return {
162
+ status: "invalid",
163
+ remediation: "The configuration file could not be read. Check its permissions before rerunning setup.",
164
+ };
165
+ }
166
+ const desired = clientConfigurationDesired(client, providerEnvironment, command);
167
+ try {
168
+ const document = client.format === "toml"
169
+ ? objectSchema.parse(parseToml(original))
170
+ : parseObject(original);
171
+ const servers = parseOptionalObject(client.format === "toml" ? document.mcp_servers : document.mcpServers);
172
+ if (sameConfiguration(servers[PRODUCT_IDENTITY.mcpServerKey], desired))
173
+ return { status: "already_current" };
174
+ }
175
+ catch {
176
+ return {
177
+ status: "invalid",
178
+ remediation: "The existing configuration is malformed. Repair it before setup applies any other changes.",
179
+ };
180
+ }
181
+ return {
182
+ status: "update",
183
+ backupPath: `${client.configPath}.rea.backup`,
184
+ };
185
+ };
186
+ const objectSchema = z.record(z.string(), z.unknown());
187
+ const parseOptionalObject = (value) => value === undefined ? {} : objectSchema.parse(value);
188
+ const parseObject = (text) => objectSchema.parse(JSON.parse(text));
189
+ const isMissing = (cause) => cause instanceof Error && "code" in cause && cause.code === "ENOENT";
190
+ const sameConfiguration = (left, right) => JSON.stringify(left) === JSON.stringify(right);
191
+ const preserveConfigBackup = async (source, destination) => {
192
+ try {
193
+ await copyFile(source, destination, fsConstants.COPYFILE_EXCL);
194
+ return true;
195
+ }
196
+ catch (cause) {
197
+ return cause instanceof Error && "code" in cause && cause.code === "EEXIST";
198
+ }
199
+ };
200
+ const restoreConfig = async (path, original) => {
201
+ try {
202
+ if (original === undefined)
203
+ await rm(path, { force: true });
204
+ else
205
+ await writeFile(path, original, { encoding: "utf8", mode: 0o600 });
206
+ }
207
+ catch {
208
+ // The backup remains available for the remediation reported by setup.
209
+ }
210
+ };
211
+ const clientConfigurationDesired = (client, providerEnvironment, command) => {
212
+ const environment = Object.fromEntries(Object.entries(providerEnvironment).sort(([left], [right]) => left.localeCompare(right)));
213
+ return {
214
+ command: command[0] ?? PRODUCT_IDENTITY.cliBinary,
215
+ args: command.slice(1),
216
+ ...(client.name === "codex"
217
+ ? { startup_timeout_sec: CODEX_STARTUP_TIMEOUT_SECONDS }
218
+ : {}),
219
+ ...(Object.keys(environment).length === 0 ? {} : { env: environment }),
220
+ };
221
+ };
222
+ const normalizeProviderEnvironment = (environment) => typeof environment === "string"
223
+ ? { HOPPER_LAUNCHER_PATH: environment }
224
+ : environment;
@@ -11,12 +11,34 @@ const failedConfigurationMessage = (reason) => {
11
11
  export const configureDetectedClients = async (options) => {
12
12
  let failure;
13
13
  for (const client of options.detectedClients) {
14
+ const actionId = `configure_client:${client.name}`;
15
+ const label = client.displayName ?? client.name;
16
+ options.onProgress?.({ actionId, label, state: "started" });
14
17
  const result = await options.host.configureClient(client, options.providerEnvironment, options.command);
15
18
  options.clients[client.name] = result;
16
- if (result.status === "failed")
19
+ if (result.status === "failed") {
17
20
  failure ??= failedConfigurationMessage(result.reason);
18
- else if (result.status === "configured")
21
+ options.onProgress?.({
22
+ actionId,
23
+ label,
24
+ state: "failed",
25
+ detail: failedConfigurationMessage(result.reason),
26
+ });
27
+ }
28
+ else if (result.status === "configured") {
19
29
  options.appliedActions.push(`configured_${client.name}`);
30
+ options.onProgress?.({ actionId, label, state: "completed" });
31
+ }
32
+ else {
33
+ options.onProgress?.({
34
+ actionId,
35
+ label,
36
+ state: "warning",
37
+ detail: result.status === "unchanged"
38
+ ? "Already current"
39
+ : "Manual configuration required",
40
+ });
41
+ }
20
42
  }
21
43
  return failure;
22
44
  };
@@ -1,38 +1,150 @@
1
+ import { homedir } from "node:os";
2
+ import { join } from "node:path";
3
+ import { PRODUCT_IDENTITY } from "../identity.js";
4
+ import { linuxHopperInstallDisclosure, } from "./LinuxHopper.js";
5
+ import { macHopperInstallDisclosure } from "./MacHopper.js";
1
6
  /** Build the complete setup mutation plan before approval. */
2
- export const setupPlan = (platform, installHopper, installSkill, clients, providerEnvironment) => [
3
- ...(installHopper
7
+ const setupPlan = (input) => [
8
+ ...(input.installHopper
4
9
  ? [
5
10
  {
11
+ id: "install_hopper",
6
12
  kind: "install_hopper",
7
- target: platform === "darwin"
8
- ? "~/Applications/Hopper Disassembler.app"
13
+ label: "Hopper deep-analysis provider",
14
+ target: input.platform === "darwin"
15
+ ? join(homedir(), "Applications/Hopper Disassembler.app")
9
16
  : "system package manager",
10
- detail: platform === "linux"
17
+ detail: input.platform === "linux"
11
18
  ? "Download, verify, and install Hopper plus its Xvfb demo-session dependencies. For the supported demo build, REA uses a private display and selects Hopper's offered demo mode for each analysis session."
12
19
  : "Download the official Hopper package, verify it, and install it. Hopper may show its demo or license prompt when first opened.",
13
20
  external: true,
21
+ operation: "install",
22
+ ...(input.platform === "linux"
23
+ ? input.linuxPackageFamily === undefined
24
+ ? {}
25
+ : linuxDisclosure(input.linuxPackageFamily)
26
+ : {
27
+ networkOrigins: macHopperInstallDisclosure.networkOrigins,
28
+ commands: macHopperInstallDisclosure.commands,
29
+ }),
14
30
  },
15
31
  ]
16
32
  : []),
17
- ...clients
18
- .filter(({ format }) => format !== "unsupported")
19
- .map((client) => ({
33
+ ...input.clients
34
+ .filter(({ client }) => client.format !== "unsupported")
35
+ .map(({ client, operation, backupPath }) => ({
36
+ id: `configure_client:${client.name}`,
20
37
  kind: "configure_client",
38
+ label: client.displayName ?? client.name,
21
39
  target: client.configPath,
22
- detail: clientConfigurationDetail(client.name, providerEnvironment),
40
+ detail: clientConfigurationDetail(client.displayName ?? client.name, input.providerEnvironment),
23
41
  external: false,
42
+ operation,
43
+ ...(backupPath === undefined ? {} : { backupPath }),
24
44
  })),
25
- ...(installSkill
45
+ ...(input.installSkill
26
46
  ? [
27
47
  {
48
+ id: "install_skill",
28
49
  kind: "install_skill",
29
- target: "~/.agents/skills/rea-analysis/SKILL.md",
30
- detail: "Install or update the bundled REA analysis skill.",
50
+ label: "REA reverse-engineering skill",
51
+ target: join(homedir(), ".agents/skills", PRODUCT_IDENTITY.skillName),
52
+ detail: "Install or update the bundled REA reverse-engineering skill and on-demand references.",
31
53
  external: false,
54
+ operation: "install",
32
55
  },
33
56
  ]
34
57
  : []),
35
58
  ];
59
+ /** Discover and preflight the complete set of selectable setup actions. */
60
+ export const discoverSetupActions = async (input) => {
61
+ const { host, providerEnvironment, command } = input;
62
+ const initialDoctor = await host.doctor();
63
+ const linuxHopperRepairNeeded = initialDoctor.checks.some(({ name, ok, detail }) => !ok &&
64
+ (name === "hopper-demo-runtime" ||
65
+ (name === "hopper-version" && detail === "/opt/hopper/bin/Hopper")));
66
+ const installHopper = input.forceHopperInstall ||
67
+ (input.proposeHopper &&
68
+ (Object.keys(providerEnvironment).length === 0 ||
69
+ linuxHopperRepairNeeded));
70
+ const [allDetectedClients, skillNeedsInstall] = await Promise.all([
71
+ host.detectedClients(),
72
+ host.skillNeedsInstall(),
73
+ ]);
74
+ const selectedClientIdSet = input.selectedClientIds === undefined
75
+ ? undefined
76
+ : new Set(input.selectedClientIds);
77
+ const detectedClients = allDetectedClients.filter(({ name }) => selectedClientIdSet === undefined || selectedClientIdSet.has(name));
78
+ const installSkill = input.installSkillSelection === false ? false : skillNeedsInstall;
79
+ const clients = installHopper
80
+ ? detectedClients
81
+ : await filterClientsNeedingConfigure({
82
+ host,
83
+ clients: detectedClients,
84
+ providerEnvironment,
85
+ command,
86
+ });
87
+ const inspections = await inspectClients({
88
+ host,
89
+ clients,
90
+ providerEnvironment,
91
+ command,
92
+ });
93
+ const invalid = inspections.find(({ inspection }) => inspection.status === "invalid");
94
+ const blocker = invalid?.inspection.status === "invalid"
95
+ ? `${invalid.client.displayName ?? invalid.client.name}: ${invalid.inspection.remediation}`
96
+ : undefined;
97
+ const clientPlans = inspections.flatMap(({ client, inspection }) => inspection.status === "create" ||
98
+ inspection.status === "update" ||
99
+ (installHopper && inspection.status === "already_current")
100
+ ? [
101
+ {
102
+ client,
103
+ operation: inspection.status === "already_current"
104
+ ? "update"
105
+ : inspection.status,
106
+ ...(inspection.status !== "already_current" &&
107
+ inspection.backupPath !== undefined
108
+ ? { backupPath: inspection.backupPath }
109
+ : {}),
110
+ },
111
+ ]
112
+ : []);
113
+ const linuxDistribution = host.platform === "linux" ? await host.linuxDistribution() : undefined;
114
+ return {
115
+ initialDoctor,
116
+ installHopper,
117
+ installSkill,
118
+ detectedClients: clientPlans.map(({ client }) => client),
119
+ plannedActions: setupPlan({
120
+ platform: host.platform,
121
+ installHopper,
122
+ installSkill,
123
+ clients: clientPlans,
124
+ providerEnvironment,
125
+ ...(linuxDistribution?.packageFamily === undefined
126
+ ? {}
127
+ : { linuxPackageFamily: linuxDistribution.packageFamily }),
128
+ }),
129
+ blocker,
130
+ };
131
+ };
132
+ const inspectClients = async (input) => Promise.all(input.clients.map(async (client) => ({
133
+ client,
134
+ inspection: (await input.host.inspectClientConfiguration?.(client, input.providerEnvironment, input.command)) ?? { status: "update" },
135
+ })));
136
+ const filterClientsNeedingConfigure = async (input) => {
137
+ const needs = await Promise.all(input.clients.map((client) => input.host.clientNeedsConfigure(client, input.providerEnvironment, input.command)));
138
+ return input.clients.filter((_, index) => needs[index]);
139
+ };
140
+ const linuxDisclosure = (family) => {
141
+ const disclosure = linuxHopperInstallDisclosure(family, process.getuid?.() === 0);
142
+ return {
143
+ networkOrigins: [disclosure.downloadUrl],
144
+ commands: disclosure.commands,
145
+ integrity: `${String(disclosure.expectedBytes)} bytes · SHA-1 ${disclosure.expectedSha1}`,
146
+ };
147
+ };
36
148
  const clientConfigurationDetail = (client, environment) => {
37
149
  const entries = Object.entries(environment)
38
150
  .sort(([left], [right]) => left.localeCompare(right))
@@ -2,59 +2,77 @@ import { mkdir, readFile, rm } from "node:fs/promises";
2
2
  import { dirname, join } from "node:path";
3
3
  import writeFileAtomic from "write-file-atomic";
4
4
  import { PRODUCT_IDENTITY } from "../identity.js";
5
- const canonicalSkillFiles = async (home) => ({
6
- destination: join(home, ".agents/skills", PRODUCT_IDENTITY.skillName, "SKILL.md"),
7
- content: await readFile(new URL(`../../skills/${PRODUCT_IDENTITY.skillName}/SKILL.md`, import.meta.url), "utf8"),
8
- });
9
- /** Report whether setup would change the managed REA skill. */
5
+ const SKILL_FILES = [
6
+ "SKILL.md",
7
+ "references/native-and-artifacts.md",
8
+ "references/javascript-applications.md",
9
+ "references/runtime-observation.md",
10
+ "references/evidence-workflows.md",
11
+ "references/controlled-replay.md",
12
+ ];
13
+ const skillRoot = (home) => join(home, ".agents/skills", PRODUCT_IDENTITY.skillName);
14
+ const readOptionalText = async (path) => {
15
+ try {
16
+ return await readFile(path, "utf8");
17
+ }
18
+ catch (cause) {
19
+ if (cause instanceof Error && "code" in cause && cause.code === "ENOENT")
20
+ return undefined;
21
+ throw cause;
22
+ }
23
+ };
24
+ const canonicalSkillFiles = async (home) => Promise.all(SKILL_FILES.map(async (relativePath) => {
25
+ const destination = join(skillRoot(home), relativePath);
26
+ return {
27
+ destination,
28
+ content: await readFile(new URL(`../../skills/${PRODUCT_IDENTITY.skillName}/${relativePath}`, import.meta.url), "utf8"),
29
+ original: await readOptionalText(destination),
30
+ };
31
+ }));
32
+ /** Report whether setup would change any file in the managed REA skill bundle. */
10
33
  export const canonicalSkillNeedsInstall = async (home) => {
11
34
  try {
12
- const { content, destination } = await canonicalSkillFiles(home);
13
- return (await readFile(destination, "utf8")) !== content;
35
+ return (await canonicalSkillFiles(home)).some(({ content, original }) => original !== content);
14
36
  }
15
37
  catch {
16
38
  return true;
17
39
  }
18
40
  };
19
- /** Transactionally install or upgrade the versioned canonical REA skill. */
41
+ const writeText = (path, content) => writeFileAtomic(path, content, { encoding: "utf8", mode: 0o600 });
42
+ const restoreSkillFiles = async (changed) => {
43
+ for (const { destination, original } of [...changed].reverse()) {
44
+ if (original === undefined)
45
+ await rm(destination, { force: true });
46
+ else
47
+ await writeText(destination, original);
48
+ }
49
+ };
50
+ /** Transactionally install or upgrade the canonical REA skill and references. */
20
51
  export const installCanonicalSkill = async (home) => {
21
- let destination = join(home, ".agents/skills", PRODUCT_IDENTITY.skillName, "SKILL.md");
22
- let backup = `${destination}.rea.backup`;
23
- let original;
52
+ let changed = [];
24
53
  try {
25
54
  const canonical = await canonicalSkillFiles(home);
26
- destination = canonical.destination;
27
- backup = `${destination}.rea.backup`;
28
- const { content } = canonical;
29
- original = await readFile(destination, "utf8").catch(() => undefined);
30
- if (original === content)
55
+ changed = canonical.filter(({ content, original }) => original !== content);
56
+ if (changed.length === 0)
31
57
  return "unchanged";
32
- await mkdir(dirname(destination), { recursive: true });
33
- if (original !== undefined)
34
- await writeFileAtomic(backup, original, {
35
- encoding: "utf8",
36
- mode: 0o600,
37
- });
38
- await writeFileAtomic(destination, content, {
39
- encoding: "utf8",
40
- mode: 0o600,
41
- });
42
- if ((await readFile(destination, "utf8")) !== content)
43
- throw new Error("skill readback mismatch");
58
+ for (const { destination, original } of changed) {
59
+ await mkdir(dirname(destination), { recursive: true });
60
+ if (original !== undefined)
61
+ await writeText(`${destination}.rea.backup`, original);
62
+ }
63
+ for (const { destination, content } of changed)
64
+ await writeText(destination, content);
65
+ for (const { destination, content } of changed)
66
+ if ((await readFile(destination, "utf8")) !== content)
67
+ throw new Error(`skill readback mismatch: ${destination}`);
44
68
  return "installed";
45
69
  }
46
70
  catch {
47
71
  try {
48
- if (original === undefined)
49
- await rm(destination, { force: true });
50
- else
51
- await writeFileAtomic(destination, original, {
52
- encoding: "utf8",
53
- mode: 0o600,
54
- });
72
+ await restoreSkillFiles(changed);
55
73
  }
56
74
  catch {
57
- // The backup remains beside the skill for explicit operator recovery.
75
+ // Per-file backups remain beside changed files for operator recovery.
58
76
  }
59
77
  return "failed";
60
78
  }
@@ -59,6 +59,7 @@ export const SUPPORTED_CLIENT_DEFINITIONS = [
59
59
  /** Describe every client location that setup, doctor, or uninstall may inspect. */
60
60
  export const supportedClients = (home) => SUPPORTED_CLIENT_DEFINITIONS.map((definition) => ({
61
61
  name: definition.name,
62
+ displayName: definition.displayName,
62
63
  configPath: join(home, ...definition.configPath),
63
64
  markerPath: join(home, ...definition.markerPath),
64
65
  format: definition.format,
@@ -39,7 +39,7 @@ export const runUninstall = async (purgeData, host = systemUninstallHost()) => {
39
39
  export const systemUninstallHost = (home = homedir(), fileSystem = systemFileSystem) => ({
40
40
  clients: () => Promise.resolve(supportedClients(home)),
41
41
  removeClient: (client) => removeClient(client, fileSystem),
42
- removeSkill: () => removeManagedPath(join(home, ".agents/skills", PRODUCT_IDENTITY.skillName), "skill", fileSystem),
42
+ removeSkill: () => removeManagedSkills(home, fileSystem),
43
43
  purgeData: async () => [
44
44
  await removeManagedPath(join(home, ".rea/cache"), "cache", fileSystem),
45
45
  await removeManagedPath(join(home, ".rea/state"), "state", fileSystem),
@@ -127,8 +127,16 @@ const isOwnedRegistration = (value) => {
127
127
  args.length === 1 &&
128
128
  args[0] === "mcp") ||
129
129
  (command === "npx" &&
130
- JSON.stringify(args) ===
131
- JSON.stringify(["-y", PRODUCT_IDENTITY.packageName, "mcp"])));
130
+ (JSON.stringify(args) ===
131
+ JSON.stringify([
132
+ "-y",
133
+ PRODUCT_IDENTITY.registrationPackageSpecifier,
134
+ "mcp",
135
+ ]) ||
136
+ JSON.stringify(args) ===
137
+ JSON.stringify(["-y", PRODUCT_IDENTITY.packageSpecifier, "mcp"]) ||
138
+ JSON.stringify(args) ===
139
+ JSON.stringify(["-y", PRODUCT_IDENTITY.packageName, "mcp"]))));
132
140
  };
133
141
  const removeManagedPath = async (path, name, fileSystem) => {
134
142
  try {
@@ -144,6 +152,7 @@ const removeManagedPath = async (path, name, fileSystem) => {
144
152
  : item(name, "failed", "This item could not be removed. Check file permissions, then rerun uninstall.");
145
153
  }
146
154
  };
155
+ const removeManagedSkills = async (home, fileSystem) => removeManagedPath(join(home, ".agents/skills", PRODUCT_IDENTITY.skillName), "skill", fileSystem);
147
156
  const item = (name, status, detail) => ({ name, status, detail });
148
157
  const objectSchema = z.record(z.string(), z.unknown());
149
158
  const optionalObject = (value) => value === undefined ? {} : objectSchema.parse(value);
@@ -9,6 +9,7 @@ import { PRODUCT_IDENTITY } from "../identity.js";
9
9
  const execFileAsync = promisify(execFile);
10
10
  const registryResponseSchema = z.object({ version: z.string().min(1) });
11
11
  const UPGRADE_COMMAND = "npm install --global rea-agents@latest";
12
+ const CLI_SCRIPT_PATH = fileURLToPath(new URL("../../scripts/rea.mjs", import.meta.url));
12
13
  /** Check the npm registry and update the same global REA installation. */
13
14
  export const runUpgrade = async (currentVersion, host = systemUpgradeHost(), output = "human") => {
14
15
  const latestVersion = await host.latestVersion();
@@ -46,6 +47,12 @@ export const runUpgrade = async (currentVersion, host = systemUpgradeHost(), out
46
47
  reason: "install",
47
48
  remediation: `REA could not update through npm. Check npm registry access and global install permissions, then run: ${UPGRADE_COMMAND}`,
48
49
  };
50
+ const integrationSyncResult = await host.syncAgentIntegration(output);
51
+ const integrationSync = integrationSyncResult === undefined
52
+ ? "deferred"
53
+ : integrationSyncResult
54
+ ? "setup_invoked"
55
+ : "failed";
49
56
  return {
50
57
  status: "upgraded",
51
58
  previousVersion: currentVersion,
@@ -53,8 +60,11 @@ export const runUpgrade = async (currentVersion, host = systemUpgradeHost(), out
53
60
  versionCheck: "available",
54
61
  installMethod: "npm",
55
62
  command: UPGRADE_COMMAND,
63
+ integrationSync,
56
64
  clientRestartRequired: true,
57
- remediation: "Rerun rea setup to refresh registrations and the skill, then restart clients that may retain an older MCP server.",
65
+ remediation: integrationSync === "setup_invoked"
66
+ ? "The updated setup journey was opened to align registrations and the skill. Restart clients whose approved registration changed."
67
+ : "Run rea setup --all-detected to align registrations and the skill, then restart clients that may retain an older MCP server.",
58
68
  };
59
69
  };
60
70
  /** Create the npm registry and subprocess effects for a production upgrade. */
@@ -79,6 +89,11 @@ export const systemUpgradeHost = () => ({
79
89
  prefix,
80
90
  `${PRODUCT_IDENTITY.packageName}@latest`,
81
91
  ], output),
92
+ syncAgentIntegration: (output) => output === "structured" ||
93
+ process.stdin.isTTY !== true ||
94
+ process.stderr.isTTY !== true
95
+ ? Promise.resolve(undefined)
96
+ : runCommand(process.execPath, [CLI_SCRIPT_PATH, "setup", "--all-detected"], "human"),
82
97
  });
83
98
  const systemNpmInstallationHost = {
84
99
  canonicalPath: realpath,