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
@@ -11,31 +11,74 @@ const investigationOptionsSchema = z.object({
11
11
  .boolean()
12
12
  .default(false)
13
13
  .describe("Approve persistent workspace writes"),
14
- workspaceName: z.string().trim().min(1).max(200).default("default"),
15
- expectedRevision: z.number().int().min(1).optional(),
14
+ workspaceName: z
15
+ .string()
16
+ .trim()
17
+ .min(1)
18
+ .max(200)
19
+ .default("default")
20
+ .describe("Logical name recorded in the investigation workspace"),
21
+ expectedRevision: z
22
+ .number()
23
+ .int()
24
+ .min(1)
25
+ .optional()
26
+ .describe("Required current workspace revision for an atomic update"),
16
27
  replayRunId: z
17
28
  .string()
18
29
  .regex(/^run_[a-f0-9]{64}$/u)
19
30
  .optional()
20
31
  .describe("Replay one verified complete workspace run without rescanning"),
21
- maxEntries: z.number().int().min(1).max(50_000).default(10_000),
32
+ maxEntries: z
33
+ .number()
34
+ .int()
35
+ .min(1)
36
+ .max(50_000)
37
+ .default(10_000)
38
+ .describe("Maximum artifact entries to inspect per version"),
22
39
  maxTotalBytes: z
23
40
  .number()
24
41
  .int()
25
42
  .min(1)
26
43
  .max(Number.MAX_SAFE_INTEGER)
27
- .default(1_073_741_824),
44
+ .default(1_073_741_824)
45
+ .describe("Maximum total uncompressed artifact content to inspect"),
28
46
  maxEntryBytes: z
29
47
  .number()
30
48
  .int()
31
49
  .min(1)
32
50
  .max(Number.MAX_SAFE_INTEGER)
33
- .default(268_435_456),
34
- pageSize: z.number().int().min(1).max(500).default(500),
35
- changeLimit: z.number().int().min(1).max(500).default(500),
36
- integrityPolicy: z.enum(["fail", "record-and-continue"]).default("fail"),
37
- integrityContinueApproved: z.boolean().default(false),
38
- maxIntegrityMismatches: z.number().int().min(1).max(100).default(10),
51
+ .default(268_435_456)
52
+ .describe("Maximum uncompressed content to inspect for one entry"),
53
+ pageSize: z
54
+ .number()
55
+ .int()
56
+ .min(1)
57
+ .max(500)
58
+ .default(500)
59
+ .describe("Maximum graph items loaded per investigation page"),
60
+ changeLimit: z
61
+ .number()
62
+ .int()
63
+ .min(1)
64
+ .max(500)
65
+ .default(500)
66
+ .describe("Maximum version changes to record"),
67
+ integrityPolicy: z
68
+ .enum(["fail", "record-and-continue"])
69
+ .default("fail")
70
+ .describe("Behavior when declared artifact integrity does not match"),
71
+ integrityContinueApproved: z
72
+ .boolean()
73
+ .default(false)
74
+ .describe("Approve continuing after recorded integrity mismatches"),
75
+ maxIntegrityMismatches: z
76
+ .number()
77
+ .int()
78
+ .min(1)
79
+ .max(100)
80
+ .default(10)
81
+ .describe("Maximum integrity mismatches to record before stopping"),
39
82
  });
40
83
  /** Register the persistent cross-version CLI workflow. */
41
84
  export const registerInvestigationCommands = (cli, logger) => {
@@ -0,0 +1,137 @@
1
+ import { z } from "incur";
2
+ const boundedCount = (subject, maximum, fallback, minimum = 1) => z
3
+ .number()
4
+ .int()
5
+ .min(minimum)
6
+ .max(maximum)
7
+ .default(fallback)
8
+ .describe(`Maximum ${subject}`);
9
+ const boundedBytes = (subject, maximum, fallback) => boundedCount(`${subject} in bytes`, maximum, fallback);
10
+ const positiveCount = (subject, fallback) => z.number().int().min(1).default(fallback).describe(`Maximum ${subject}`);
11
+ const browserScopeOptions = {
12
+ allowedOrigins: z
13
+ .array(z.string().min(1))
14
+ .optional()
15
+ .describe("Exact origins to observe; defaults to REA_BROWSER_ALLOWED_ORIGINS_JSON"),
16
+ approved: z.boolean().default(false).describe("Approve passive observation"),
17
+ };
18
+ export const browserPageInspectionOptions = z.object({
19
+ ...browserScopeOptions,
20
+ observationMs: boundedCount("observation duration in milliseconds", 10_000, 500, 0),
21
+ includeAccessibilityText: z
22
+ .boolean()
23
+ .default(false)
24
+ .describe("Include bounded accessibility text"),
25
+ includeConsoleText: z
26
+ .boolean()
27
+ .default(false)
28
+ .describe("Include bounded console message text"),
29
+ consoleTextApproved: z
30
+ .boolean()
31
+ .default(false)
32
+ .describe("Approve capturing console message text"),
33
+ includeJsonBodyShapes: z
34
+ .boolean()
35
+ .default(false)
36
+ .describe("Include structural shapes of JSON response bodies"),
37
+ jsonBodySchemaApproved: z
38
+ .boolean()
39
+ .default(false)
40
+ .describe("Approve inspecting JSON response bodies for structural shapes"),
41
+ includeWebsocketShapes: z
42
+ .boolean()
43
+ .default(false)
44
+ .describe("Include structural shapes of WebSocket payloads"),
45
+ websocketShapeApproved: z
46
+ .boolean()
47
+ .default(false)
48
+ .describe("Approve inspecting WebSocket payloads for structural shapes"),
49
+ includeScriptSources: z
50
+ .boolean()
51
+ .default(false)
52
+ .describe("Include bounded JavaScript source text"),
53
+ includeStorageKeys: z
54
+ .boolean()
55
+ .default(false)
56
+ .describe("Include storage key names without values"),
57
+ maxFrames: boundedCount("page frames", 1_000, 200),
58
+ maxDomNodes: boundedCount("DOM nodes", 10_000, 2_000),
59
+ maxAxNodes: boundedCount("accessibility nodes", 10_000, 2_000),
60
+ maxAxTextFieldBytes: boundedBytes("one accessibility text field", 16 * 1_024, 1_024),
61
+ maxTotalAxTextBytes: boundedBytes("total accessibility text", 1_024 * 1_024, 64 * 1_024),
62
+ maxScripts: boundedCount("scripts", 1_000, 200),
63
+ maxResources: boundedCount("resources", 10_000, 2_000),
64
+ maxWorkers: boundedCount("workers", 5_000, 500),
65
+ maxStorageKeys: boundedCount("storage keys", 10_000, 1_000),
66
+ maxScriptSourceBytes: boundedBytes("one script source", 4 * 1_024 * 1_024, 1_024 * 1_024),
67
+ maxTotalScriptSourceBytes: boundedBytes("total script source", 16 * 1_024 * 1_024, 4 * 1_024 * 1_024),
68
+ maxNetworkEvents: boundedCount("network events", 10_000, 1_000),
69
+ maxConsoleEvents: boundedCount("console events", 2_000, 200),
70
+ maxConsoleTextFieldBytes: boundedBytes("one console text field", 16 * 1_024, 1_024),
71
+ maxTotalConsoleTextBytes: boundedBytes("total console text", 1_024 * 1_024, 64 * 1_024),
72
+ maxJsonBodyBytes: boundedBytes("one JSON response body", 4 * 1_024 * 1_024, 1_024 * 1_024),
73
+ maxTotalJsonBodyBytes: boundedBytes("total JSON response bodies", 16 * 1_024 * 1_024, 4 * 1_024 * 1_024),
74
+ maxJsonShapeNodes: boundedCount("JSON shape nodes", 100_000, 5_000),
75
+ maxJsonShapeDepth: boundedCount("JSON shape depth", 100, 20),
76
+ maxWebsocketEvents: boundedCount("WebSocket events", 5_000, 500),
77
+ maxWebsocketShapeBytes: boundedBytes("one WebSocket shape", 1_024 * 1_024, 64 * 1_024),
78
+ maxTotalWebsocketShapeBytes: boundedBytes("total WebSocket shapes", 16 * 1_024 * 1_024, 1_024 * 1_024),
79
+ });
80
+ const electronScopeOptions = {
81
+ allowedFileRoots: z
82
+ .array(z.string().min(1))
83
+ .optional()
84
+ .describe("Filesystem roots; defaults to REA_ELECTRON_FILE_ROOTS_JSON"),
85
+ approved: z.boolean().default(false).describe("Approve passive observation"),
86
+ };
87
+ export const electronPageInspectionOptions = z.object({
88
+ ...electronScopeOptions,
89
+ observationMs: boundedCount("observation duration in milliseconds", 10_000, 100, 0),
90
+ includeScriptSources: z
91
+ .boolean()
92
+ .default(false)
93
+ .describe("Include bounded JavaScript source text"),
94
+ sourceCaptureApproved: z
95
+ .boolean()
96
+ .default(false)
97
+ .describe("Approve capturing bounded script source text"),
98
+ maxFrames: boundedCount("page frames", 1_000, 200),
99
+ maxDomNodes: boundedCount("DOM nodes", 10_000, 2_000),
100
+ maxScripts: boundedCount("scripts", 2_000, 500),
101
+ maxResources: boundedCount("resources", 10_000, 2_000),
102
+ maxWorkers: boundedCount("workers", 5_000, 500),
103
+ maxScriptSourceBytes: boundedBytes("one captured script source", 4 * 1_024 * 1_024, 1_024 * 1_024),
104
+ maxTotalScriptSourceBytes: boundedBytes("total captured script source", 16 * 1_024 * 1_024, 4 * 1_024 * 1_024),
105
+ });
106
+ export const javascriptApplicationOptions = z.object({
107
+ approved: z
108
+ .boolean()
109
+ .default(false)
110
+ .describe("Approve reading the application artifact"),
111
+ format: z
112
+ .enum(["auto", "asar", "directory"])
113
+ .default("auto")
114
+ .describe("Application artifact format"),
115
+ sourceMapReadApproved: z
116
+ .boolean()
117
+ .default(false)
118
+ .describe("Approve reading local source maps referenced by the application"),
119
+ maxEntries: positiveCount("artifact entries to inspect", 8_000),
120
+ maxTotalArtifactBytes: positiveCount("total uncompressed artifact content to inspect", 512 * 1_024 * 1_024),
121
+ maxArtifactEntryBytes: positiveCount("uncompressed content for one artifact entry", 128 * 1_024 * 1_024),
122
+ maxCompressionRatio: z
123
+ .number()
124
+ .min(1)
125
+ .default(1_000)
126
+ .describe("Maximum accepted compressed-to-uncompressed expansion ratio"),
127
+ maxDepth: positiveCount("artifact directory depth", 64),
128
+ maxPathBytes: positiveCount("encoded artifact path length", 4_096),
129
+ maxTextFiles: positiveCount("text files to parse", 5_000),
130
+ maxTotalTextBytes: positiveCount("total text content to parse", 128 * 1_024 * 1_024),
131
+ maxTextFileBytes: positiveCount("size of one parsed text file", 8 * 1_024 * 1_024),
132
+ maxAstNodes: positiveCount("JavaScript AST nodes to parse", 2_000_000),
133
+ maxFindings: positiveCount("static-analysis findings to return", 8_000),
134
+ maxModules: positiveCount("application modules to project", 20_000),
135
+ maxSourceMapSources: positiveCount("original sources from source maps", 5_000),
136
+ maxParseMilliseconds: positiveCount("JavaScript parsing time in milliseconds", 30_000),
137
+ });
@@ -36,17 +36,40 @@ export const registerPolicyCommands = (cli, logger) => {
36
36
  cli.command(CLI_COMMANDS.policy, {
37
37
  description: "Inspect, explain, or revoke unified local permission grants",
38
38
  args: z.object({
39
- action: z.enum(["status", "list", "explain", "revoke"]),
40
- value: z.string().optional(),
39
+ action: z
40
+ .enum(["status", "list", "explain", "revoke"])
41
+ .describe("Permission-policy operation to perform"),
42
+ value: z
43
+ .string()
44
+ .optional()
45
+ .describe("Capability to explain or grant ID to revoke"),
41
46
  }),
42
47
  options: z.object({
43
- root: z.string().optional(),
44
- executable: z.string().optional(),
45
- environmentNames: z.array(z.string()).default([]),
46
- origins: z.array(z.string()).default([]),
47
- network: z.enum(["none", "loopback", "external"]).default("none"),
48
- mount: z.boolean().default(false),
49
- write: z.boolean().default(false),
48
+ root: z.string().optional().describe("Filesystem root to authorize"),
49
+ executable: z
50
+ .string()
51
+ .optional()
52
+ .describe("Executable path to authorize"),
53
+ environmentNames: z
54
+ .array(z.string())
55
+ .default([])
56
+ .describe("Environment variable names the operation may read"),
57
+ origins: z
58
+ .array(z.string())
59
+ .default([])
60
+ .describe("Exact browser origins the operation may access"),
61
+ network: z
62
+ .enum(["none", "loopback", "external"])
63
+ .default("none")
64
+ .describe("Maximum network scope to authorize"),
65
+ mount: z
66
+ .boolean()
67
+ .default(false)
68
+ .describe("Authorize mounting an artifact filesystem"),
69
+ write: z
70
+ .boolean()
71
+ .default(false)
72
+ .describe("Explain the write form of the requested capability"),
50
73
  yes: z
51
74
  .boolean()
52
75
  .default(false)
@@ -58,56 +81,67 @@ export const registerPolicyCommands = (cli, logger) => {
58
81
  if (!config.ok)
59
82
  return failure(config.error);
60
83
  if (args.action === "status")
61
- return {
62
- reload: "SIGHUP",
63
- restart_required: false,
64
- administrator_ceilings: config.value.permissionCeilings,
65
- project_store: config.value.permissionProjectStore ?? null,
66
- };
84
+ return policyStatus(config.value);
67
85
  if (args.action === "list")
68
- return readProjectGrants(config.value).then((result) => result.ok ? { grants: result.grants } : result.error);
69
- if (args.action === "revoke") {
70
- if (args.value === undefined)
71
- return { error: "Grant ID is required for policy revoke" };
72
- const { permissionProjectRoot, permissionProjectStore } = config.value;
73
- if (permissionProjectRoot === undefined ||
74
- permissionProjectStore === undefined)
75
- return { error: "Project policy is not configured" };
76
- const approval = await approvePolicyRevocation({
86
+ return policyList(config.value);
87
+ if (args.action === "revoke")
88
+ return policyRevoke({
89
+ config: config.value,
90
+ grantId: args.value,
77
91
  approved: options.yes,
78
92
  interactive: !agent && !formatExplicit && process.stdin.isTTY === true,
79
- grantId: args.value,
80
93
  confirm: confirmPolicyRevocation,
81
94
  });
82
- if (!approval.approved)
83
- return approval.reason === "required"
84
- ? {
85
- error: "Policy revoke requires confirmation. Rerun interactively or with --yes.",
86
- grant_id: args.value,
87
- }
88
- : {
89
- error: "Policy revocation was cancelled",
90
- grant_id: args.value,
91
- };
92
- const revoked = await revokeProjectPermissionGrant(permissionProjectStore, permissionProjectRoot, args.value);
93
- return revoked.ok && revoked.value
94
- ? {
95
- revoked: args.value,
96
- reload: "Send SIGHUP to the REA MCP process",
97
- }
98
- : revoked.ok
99
- ? { error: "Grant was not found", grant_id: args.value }
100
- : { error: revoked.error.message };
101
- }
102
95
  const capability = capabilitySchema.safeParse(args.value);
103
96
  if (!capability.success)
104
97
  return {
105
98
  error: "A registered capability is required for policy explain",
106
99
  };
107
- return explain(config.value, capability.data, options);
100
+ return policyExplain(config.value, capability.data, options);
108
101
  }),
109
102
  });
110
103
  };
104
+ const policyStatus = (config) => ({
105
+ reload: "SIGHUP",
106
+ restart_required: false,
107
+ administrator_ceilings: config.permissionCeilings,
108
+ project_store: config.permissionProjectStore ?? null,
109
+ });
110
+ const policyList = (config) => readProjectGrants(config).then((result) => result.ok ? { grants: result.grants } : result.error);
111
+ const policyRevoke = async (context) => {
112
+ if (context.grantId === undefined)
113
+ return { error: "Grant ID is required for policy revoke" };
114
+ const { permissionProjectRoot, permissionProjectStore } = context.config;
115
+ if (permissionProjectRoot === undefined ||
116
+ permissionProjectStore === undefined)
117
+ return { error: "Project policy is not configured" };
118
+ const approval = await approvePolicyRevocation({
119
+ approved: context.approved,
120
+ interactive: context.interactive,
121
+ grantId: context.grantId,
122
+ confirm: context.confirm,
123
+ });
124
+ if (!approval.approved)
125
+ return approval.reason === "required"
126
+ ? {
127
+ error: "Policy revoke requires confirmation. Rerun interactively or with --yes.",
128
+ grant_id: context.grantId,
129
+ }
130
+ : {
131
+ error: "Policy revocation was cancelled",
132
+ grant_id: context.grantId,
133
+ };
134
+ const revoked = await revokeProjectPermissionGrant(permissionProjectStore, permissionProjectRoot, context.grantId);
135
+ return revoked.ok && revoked.value
136
+ ? {
137
+ revoked: context.grantId,
138
+ reload: "Send SIGHUP to the REA MCP process",
139
+ }
140
+ : revoked.ok
141
+ ? { error: "Grant was not found", grant_id: context.grantId }
142
+ : { error: revoked.error.message };
143
+ };
144
+ const policyExplain = async (config, capability, options) => explain(config, capability, options);
111
145
  const confirmPolicyRevocation = async (grantId) => {
112
146
  const prompt = createInterface({
113
147
  input: process.stdin,
@@ -0,0 +1,306 @@
1
+ import { cancel, confirm, intro, isCancel, multiselect, outro, } from "@clack/prompts";
2
+ import { SUPPORTED_CLIENT_DEFINITIONS } from "./application/SupportedClients.js";
3
+ const clientDisplayNames = new Map(SUPPORTED_CLIENT_DEFINITIONS.map(({ name, displayName }) => [
4
+ name,
5
+ displayName,
6
+ ]));
7
+ const promptStreams = {
8
+ input: process.stdin,
9
+ output: process.stderr,
10
+ withGuide: true,
11
+ };
12
+ const agentIntegrationCapabilityId = "agent_integration";
13
+ /** Run the inline setup picker, exact preflight, and default-No consent. */
14
+ export const confirmInteractiveSetup = async (actions, accessible) => {
15
+ intro("REA setup", promptStreams);
16
+ renderValueIntroduction();
17
+ renderDetectedSummary(actions);
18
+ renderKeyboardHelp(accessible);
19
+ const selectedActionIds = await selectSetupActions(actions, accessible);
20
+ if (selectedActionIds === undefined)
21
+ return cancelledDecision();
22
+ if (selectedActionIds.length === 0) {
23
+ cancel("Nothing selected. No changes were made.", promptStreams);
24
+ return { approved: false, selectedActionIds };
25
+ }
26
+ const selected = new Set(selectedActionIds);
27
+ const selectedActions = actions.filter(({ id }) => selected.has(id));
28
+ renderPreflight(selectedActions);
29
+ const approved = await confirm({
30
+ ...promptStreams,
31
+ message: selectedActions.length === 1
32
+ ? "Apply this change?"
33
+ : `Apply these ${String(selectedActions.length)} changes?`,
34
+ initialValue: false,
35
+ active: "Yes, apply",
36
+ inactive: "No, cancel",
37
+ });
38
+ if (isCancel(approved) || !approved) {
39
+ cancel("Setup cancelled. No changes were made.", promptStreams);
40
+ return { approved: false, selectedActionIds };
41
+ }
42
+ return { approved: true, selectedActionIds };
43
+ };
44
+ /** Render stable, append-only progress for real setup operations. */
45
+ export const renderSetupProgress = (event) => {
46
+ const symbol = event.state === "started"
47
+ ? "◇"
48
+ : event.state === "completed"
49
+ ? "◆"
50
+ : event.state === "warning"
51
+ ? "!"
52
+ : "✗";
53
+ const suffix = event.detail === undefined ? "" : ` · ${event.detail}`;
54
+ writeLine(`${symbol} ${event.label}${suffix}`);
55
+ };
56
+ /** Finish an interactive setup journey without exposing the full doctor catalog. */
57
+ export const renderInteractiveSetupResult = (result) => {
58
+ if (result.status === "ready") {
59
+ const changedClients = Object.entries(result.clients)
60
+ .filter(([, client]) => client.status === "configured")
61
+ .map(([client]) => clientDisplayNames.get(client) ?? client);
62
+ const readyClients = readyAgentClients(result, changedClients);
63
+ renderReadyCapabilities(result, readyClients);
64
+ outro(changedClients.length === 0
65
+ ? "REA is ready for local app analysis."
66
+ : `Restart ${changedClients.join(", ")} to load REA.`, promptStreams);
67
+ return;
68
+ }
69
+ if (result.status === "planned")
70
+ return;
71
+ writeLine(`! ${result.remediation ?? "Setup needs attention."}`);
72
+ outro("Run `rea doctor` for the remaining checks.", promptStreams);
73
+ };
74
+ /** Keep default non-TTY output actionable without emitting the full catalog. */
75
+ export const conciseSetupResult = (result) => ({
76
+ status: result.status,
77
+ plannedActions: result.plannedActions,
78
+ appliedActions: result.appliedActions,
79
+ clients: result.clients,
80
+ doctor: {
81
+ healthy: result.doctor.healthy,
82
+ checks: result.doctor.checks.filter(({ ok }) => !ok),
83
+ },
84
+ ...(result.code === undefined ? {} : { code: result.code }),
85
+ ...(result.remediation === undefined
86
+ ? {}
87
+ : { remediation: result.remediation }),
88
+ });
89
+ const selectSetupActions = async (actions, accessible) => {
90
+ const clientActions = actions.filter(({ kind }) => kind === "configure_client");
91
+ const componentActions = actions.filter(({ kind }) => kind !== "configure_client");
92
+ const capabilities = setupCapabilities(clientActions, componentActions);
93
+ const selectedCapabilities = accessible
94
+ ? await selectCapabilitiesAccessibly(capabilities)
95
+ : await selectCapabilities(capabilities);
96
+ if (selectedCapabilities === undefined)
97
+ return undefined;
98
+ const selectedCapabilityIds = new Set(selectedCapabilities);
99
+ const bundledSkillAction = componentActions.find(({ kind }) => kind === "install_skill");
100
+ const selectedActionIds = componentActions
101
+ .filter(({ id }) => id !== bundledSkillAction?.id && selectedCapabilityIds.has(id))
102
+ .map(({ id }) => id);
103
+ if (!selectedCapabilityIds.has(agentIntegrationCapabilityId))
104
+ return selectedActionIds;
105
+ if (clientActions.length === 0)
106
+ return [
107
+ ...(bundledSkillAction === undefined ? [] : [bundledSkillAction.id]),
108
+ ...selectedActionIds,
109
+ ];
110
+ const selectedClients = accessible
111
+ ? await selectClientsAccessibly(clientActions)
112
+ : await selectClients(clientActions);
113
+ if (selectedClients === undefined)
114
+ return undefined;
115
+ if (selectedClients.length === 0)
116
+ return selectedActionIds;
117
+ return [
118
+ ...selectedClients,
119
+ ...(bundledSkillAction === undefined ? [] : [bundledSkillAction.id]),
120
+ ...selectedActionIds,
121
+ ];
122
+ };
123
+ const setupCapabilities = (clientActions, componentActions) => [
124
+ ...(clientActions.length === 0 &&
125
+ !componentActions.some(({ kind }) => kind === "install_skill")
126
+ ? []
127
+ : [
128
+ {
129
+ id: agentIntegrationCapabilityId,
130
+ label: "Agent integration (MCP + guided workflow)",
131
+ hint: clientActions.length === 0
132
+ ? "MCP registrations are aligned; installs the matching bundled skill"
133
+ : `${String(clientActions.length)} detected ${clientActions.length === 1 ? "agent" : "agents"}; installs the bundled skill when needed`,
134
+ },
135
+ ]),
136
+ ...componentActions
137
+ .filter(({ kind }) => kind !== "install_skill")
138
+ .map((action) => ({
139
+ id: action.id,
140
+ label: `${action.label} (${actionModality(action)})`,
141
+ hint: `${action.operation} · ${action.target}`,
142
+ })),
143
+ ];
144
+ const selectCapabilities = async (capabilities) => {
145
+ const selection = await multiselect({
146
+ ...promptStreams,
147
+ message: "What should REA set up?",
148
+ options: capabilities.map((capability) => ({
149
+ value: capability.id,
150
+ label: capability.label,
151
+ hint: capability.hint,
152
+ })),
153
+ required: false,
154
+ maxItems: Math.max(3, Math.min(capabilities.length, (process.stderr.rows ?? 24) - 8)),
155
+ showInstructions: true,
156
+ });
157
+ return isCancel(selection) ? undefined : selection;
158
+ };
159
+ const selectCapabilitiesAccessibly = async (capabilities) => {
160
+ const selected = [];
161
+ for (const capability of capabilities) {
162
+ const included = await confirm({
163
+ ...promptStreams,
164
+ message: `Set up ${capability.label}? ${capability.hint}`,
165
+ initialValue: false,
166
+ vertical: true,
167
+ });
168
+ if (isCancel(included))
169
+ return undefined;
170
+ if (included)
171
+ selected.push(capability.id);
172
+ }
173
+ return selected;
174
+ };
175
+ const selectClients = async (actions) => {
176
+ const selection = await multiselect({
177
+ ...promptStreams,
178
+ message: "Which agents should use REA?",
179
+ options: actions.map((action) => ({
180
+ value: action.id,
181
+ label: `${action.label} (detected)`,
182
+ hint: `${action.operation} · ${action.target}`,
183
+ })),
184
+ required: false,
185
+ maxItems: Math.max(3, Math.min(actions.length, (process.stderr.rows ?? 24) - 8)),
186
+ showInstructions: true,
187
+ });
188
+ return isCancel(selection) ? undefined : selection;
189
+ };
190
+ const selectClientsAccessibly = async (actions) => {
191
+ const selected = [];
192
+ for (const action of actions) {
193
+ const included = await confirm({
194
+ ...promptStreams,
195
+ message: `Configure ${action.label}? ${action.target}`,
196
+ initialValue: false,
197
+ vertical: true,
198
+ });
199
+ if (isCancel(included))
200
+ return undefined;
201
+ if (included)
202
+ selected.push(action.id);
203
+ }
204
+ return selected;
205
+ };
206
+ const renderValueIntroduction = () => {
207
+ writeLine("│ Understand local apps and binaries from your coding agent.");
208
+ writeLine("│");
209
+ writeLine("│ • Trace how a feature works with local evidence.");
210
+ writeLine("│ • Use the same analysis capabilities from REA's CLI and supported agents.");
211
+ writeLine("│ • Keep analyzed targets on this machine instead of uploading them to a hosted service.");
212
+ };
213
+ const renderDetectedSummary = (actions) => {
214
+ const clients = actions.filter(({ kind }) => kind === "configure_client");
215
+ writeLine("│");
216
+ if (clients.length === 0) {
217
+ writeLine("◆ No agent integrations need configuration");
218
+ }
219
+ else {
220
+ writeLine(`◆ Found ${String(clients.length)} supported ${clients.length === 1 ? "agent" : "agents"}`);
221
+ writeLine(`│ ${humanList(clients.map(({ label }) => label))}`);
222
+ }
223
+ };
224
+ const renderKeyboardHelp = (accessible) => {
225
+ writeLine("│");
226
+ writeLine(accessible
227
+ ? "│ Keys: Enter answer · Ctrl-C cancel"
228
+ : "│ Keys: ↑/↓ navigate · Space toggle · Enter confirm · Ctrl-C cancel");
229
+ };
230
+ const renderReadyCapabilities = (result, readyClients) => {
231
+ writeLine("◆ What you can do now");
232
+ const providers = readyProviders(result);
233
+ if (providers.length > 0)
234
+ writeLine(`│ Deep analysis: ${humanList(providers)}`);
235
+ if (readyClients.length > 0)
236
+ writeLine(`│ Agent access: ${humanList(readyClients)}`);
237
+ if (result.doctor.identity?.skill.state === "aligned")
238
+ writeLine("│ Guided reverse-engineering workflows: installed");
239
+ if (providers.length > 0)
240
+ writeLine("│ CLI: rea analyze /path/to/app");
241
+ const firstClient = readyClients[0];
242
+ if (firstClient !== undefined)
243
+ writeLine(`│ Try in ${firstClient}: "Explain how a feature works in /path/to/app and show the evidence."`);
244
+ };
245
+ const readyAgentClients = (result, changedClients) => {
246
+ const alignedClients = (result.doctor.identity?.registrations ?? [])
247
+ .filter(({ state }) => state === "aligned")
248
+ .map(({ client }) => clientDisplayNames.get(client) ?? client);
249
+ return [...new Set([...alignedClients, ...changedClients])];
250
+ };
251
+ const readyProviders = (result) => {
252
+ const providers = [
253
+ ...(result.doctor.hopperPath === undefined ? [] : ["Hopper"]),
254
+ ...(result.doctor.providerInspections ?? [])
255
+ .filter(({ available }) => available)
256
+ .map(({ id }) => displayProviderId(id)),
257
+ ];
258
+ return [...new Set(providers)];
259
+ };
260
+ const displayProviderId = (id) => id.length === 0 ? id : `${id[0]?.toUpperCase() ?? ""}${id.slice(1)}`;
261
+ const actionModality = (action) => {
262
+ if (action.kind === "configure_client")
263
+ return "MCP";
264
+ if (action.kind === "install_hopper")
265
+ return "provider";
266
+ return "skill";
267
+ };
268
+ const humanList = (values) => {
269
+ if (values.length < 2)
270
+ return values[0] ?? "";
271
+ if (values.length === 2)
272
+ return `${values[0]} and ${values[1]}`;
273
+ return `${values.slice(0, -1).join(", ")}, and ${values.at(-1)}`;
274
+ };
275
+ const renderPreflight = (actions) => {
276
+ writeLine("│");
277
+ writeLine("◆ Ready to review");
278
+ for (const action of actions) {
279
+ writeLine("│");
280
+ writeLine(`│ ${action.operation.toUpperCase()} ${action.label}`);
281
+ writeLine(`│ ${action.target}`);
282
+ if (action.backupPath !== undefined)
283
+ writeLine(`│ backup: ${action.backupPath}`);
284
+ for (const origin of action.networkOrigins ?? [])
285
+ writeLine(`│ network: ${origin}`);
286
+ if (action.integrity !== undefined)
287
+ writeLine(`│ integrity: ${action.integrity}`);
288
+ for (const command of action.commands ?? [])
289
+ writeLine(`│ command: ${command}`);
290
+ writeLine(`│ ${action.detail}`);
291
+ if (action.external)
292
+ writeLine("│ external software");
293
+ }
294
+ writeLine("│");
295
+ writeLine("│ REA will preserve unrelated configuration and will not install or upgrade Node.js, npm, Homebrew, Java, or Ghidra.");
296
+ };
297
+ const cancelledDecision = () => {
298
+ cancel("Setup cancelled. No changes were made.", promptStreams);
299
+ return {
300
+ approved: false,
301
+ selectedActionIds: [],
302
+ };
303
+ };
304
+ const writeLine = (line) => {
305
+ process.stderr.write(`${line}\n`);
306
+ };