rea-agents 1.0.0 → 1.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 (52) hide show
  1. package/README.md +49 -18
  2. package/dist/application/ArtifactInventory.js +80 -44
  3. package/dist/application/CommandShimReplay.js +136 -0
  4. package/dist/application/Doctor.js +4 -4
  5. package/dist/application/EvidenceBundleFiles.js +13 -4
  6. package/dist/application/MacHopper.js +175 -0
  7. package/dist/application/ProcessCaptureAuthority.js +25 -0
  8. package/dist/application/ProcessCaptureCapability.js +23 -0
  9. package/dist/application/ProcessCaptureError.js +5 -0
  10. package/dist/application/ProcessCaptureLifecycle.js +269 -0
  11. package/dist/application/ProcessCheckpoints.js +129 -0
  12. package/dist/application/ProcessCli.js +61 -0
  13. package/dist/application/ProcessEvidence.js +38 -0
  14. package/dist/application/ProcessHarness.js +252 -272
  15. package/dist/application/ProcessNormalization.js +10 -1
  16. package/dist/application/ProcessOwnership.js +39 -1
  17. package/dist/application/ProcessSampling.js +39 -23
  18. package/dist/application/Setup.js +104 -100
  19. package/dist/application/SetupSkill.js +44 -0
  20. package/dist/application/TerminalRenderer.js +92 -0
  21. package/dist/application/runtime.js +1 -1
  22. package/dist/artifacts/ArtifactProvider.js +18 -6
  23. package/dist/artifacts/ArtifactReader.js +3 -1
  24. package/dist/artifacts/AsarArtifactReader.js +8 -1
  25. package/dist/artifacts/DirectoryArtifactReader.js +1 -0
  26. package/dist/artifacts/MachOSliceArtifactReader.js +1 -0
  27. package/dist/artifacts/NativeDmgArtifactReader.js +151 -0
  28. package/dist/artifacts/ZipArtifactReader.js +1 -0
  29. package/dist/cli.js +60 -26
  30. package/dist/cliProcessCommands.js +19 -0
  31. package/dist/config.js +2 -0
  32. package/dist/contracts/artifactToolContracts.js +1 -0
  33. package/dist/contracts/investigationExamples.js +3 -3
  34. package/dist/contracts/processCaptureExample.js +50 -7
  35. package/dist/contracts/toolContracts.js +3 -2
  36. package/dist/domain/changedBehavior.js +2 -2
  37. package/dist/domain/errors.js +11 -3
  38. package/dist/domain/processCapture.js +99 -293
  39. package/dist/domain/processCaptureValidation.js +124 -0
  40. package/dist/domain/processComparison.js +176 -32
  41. package/dist/domain/processScenario.js +370 -0
  42. package/dist/domain/reconstructionVerification.js +3 -3
  43. package/dist/domain/runtimeVersion.js +8 -0
  44. package/dist/domain/staticRuntimeCorrelation.js +3 -2
  45. package/dist/identity.js +1 -0
  46. package/dist/server/registerProcessComparisonTool.js +28 -8
  47. package/dist/server/registerSessionTools.js +3 -25
  48. package/dist/server/sessionToolPolicies.js +1 -6
  49. package/install.sh +79 -164
  50. package/package.json +7 -4
  51. package/scripts/prepare-node-pty.mjs +35 -0
  52. package/skills/rea-analysis/SKILL.md +19 -4
@@ -4,14 +4,14 @@ import { SESSION_TOOL_CONTRACTS } from "../contracts/toolContracts.js";
4
4
  import { createEvidence } from "../domain/evidence.js";
5
5
  import { EvidenceIntegrityError } from "../domain/errors.js";
6
6
  import { jsonValueSchema } from "../domain/jsonValue.js";
7
- import { compareProcessCaptures, processCaptureSchema, } from "../domain/processCapture.js";
7
+ import { compareProcessCaptures, processCaptureSchema, parseProcessCapture, } from "../domain/processCapture.js";
8
8
  import { recordDerivedEvidence } from "./recordDerivedEvidence.js";
9
9
  import { err } from "../domain/result.js";
10
10
  import { PROCESS_PROVIDER } from "./sessionToolPolicies.js";
11
11
  import { toCallToolResult } from "./toolResult.js";
12
12
  import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
13
13
  /** Register deterministic process-capture comparison and contradiction tracking. */
14
- export const registerProcessComparisonTool = (server, session, contract) => {
14
+ export const registerProcessComparisonTool = (server, session, contract, now = Date.now) => {
15
15
  server.registerTool(contract.name, toolRegistrationOptions(contract), (input) => {
16
16
  const parsed = z
17
17
  .object({
@@ -19,15 +19,29 @@ export const registerProcessComparisonTool = (server, session, contract) => {
19
19
  left: processCaptureSchema,
20
20
  right_evidence_id: z.string(),
21
21
  right: processCaptureSchema,
22
+ max_capture_age_ms: z.number().int().nonnegative().optional(),
22
23
  unknown_registry_approved: z.literal(true).optional(),
23
24
  })
24
25
  .parse(input);
25
26
  const validated = validateCaptureSources(session, parsed);
26
27
  if (!validated.ok)
27
28
  return toCallToolResult(validated, contract);
28
- const comparison = compareProcessCaptures(parsed.left, parsed.right);
29
+ let comparison;
30
+ try {
31
+ comparison = compareProcessCaptures(parsed.left, parsed.right, {
32
+ ...(parsed.max_capture_age_ms === undefined
33
+ ? {}
34
+ : { maxCaptureAgeMs: parsed.max_capture_age_ms }),
35
+ now,
36
+ });
37
+ }
38
+ catch (cause) {
39
+ return toCallToolResult(err(new EvidenceIntegrityError(cause instanceof Error
40
+ ? cause.message
41
+ : "Invalid Process Capture v4")), contract);
42
+ }
29
43
  const evidence = createEvidence(undefined, PROCESS_PROVIDER, {
30
- predicateType: "rea.process-comparison/v1",
44
+ predicateType: "rea.process-comparison/v3",
31
45
  operation: contract.name,
32
46
  parameters: {
33
47
  left_evidence_id: parsed.left_evidence_id,
@@ -50,17 +64,23 @@ const validateCaptureSources = (session, parsed) => {
50
64
  [parsed.right_evidence_id, parsed.right],
51
65
  ]) {
52
66
  const evidence = session.evidenceById(evidenceId);
53
- const result = processCaptureSchema.safeParse(evidence?.normalized_result);
67
+ let evidenceCapture;
68
+ try {
69
+ evidenceCapture = parseProcessCapture(evidence?.normalized_result);
70
+ }
71
+ catch {
72
+ evidenceCapture = undefined;
73
+ }
54
74
  if (evidence === undefined ||
55
75
  evidence.operation !== "capture_process_scenario" ||
56
- evidence.predicate_type !== "rea.process-capture/v2" ||
76
+ evidence.predicate_type !== "rea.process-capture/v4" ||
57
77
  evidence.provider.id !== PROCESS_PROVIDER.id ||
58
78
  evidence.provider.name !== PROCESS_PROVIDER.name ||
59
79
  evidence.provider.version !== PROCESS_PROVIDER.version ||
60
80
  evidence.confidence !== "observed" ||
61
81
  evidence.authority !== "controlled-replay" ||
62
- !result.success ||
63
- canonicalJson(result.data) !== canonicalJson(capture))
82
+ evidenceCapture === undefined ||
83
+ canonicalJson(evidenceCapture) !== canonicalJson(capture))
64
84
  return err(new EvidenceIntegrityError("Process comparison payload does not match its source Evidence"));
65
85
  }
66
86
  return { ok: true, value: null };
@@ -5,12 +5,11 @@ import { logToolExecution } from "./toolLogging.js";
5
5
  import { ok } from "../domain/result.js";
6
6
  import { processScenarioSchema } from "../domain/processCapture.js";
7
7
  import { captureProcessScenario } from "../application/ProcessHarness.js";
8
- import { createEvidence } from "../domain/evidence.js";
9
- import { jsonValueSchema } from "../domain/jsonValue.js";
10
8
  import { recordUnknownInputSchema, updateUnknownInputSchema, } from "../domain/residualUnknown.js";
11
9
  import { readEvidenceBundle, writeEvidenceBundle, } from "../application/EvidenceBundleFiles.js";
12
10
  import { UnknownRegistryError } from "../domain/errors.js";
13
- import { DENY_EVIDENCE_FILE_POLICY, DENY_PROCESS_POLICY, PROCESS_PROVIDER, } from "./sessionToolPolicies.js";
11
+ import { DENY_EVIDENCE_FILE_POLICY, DENY_PROCESS_POLICY, } from "./sessionToolPolicies.js";
12
+ import { createProcessCaptureEvidence } from "../application/ProcessEvidence.js";
14
13
  import { registerProcessComparisonTool } from "./registerProcessComparisonTool.js";
15
14
  import { registerArtifactComparisonTool } from "./registerArtifactComparisonTool.js";
16
15
  import { registerFunctionComparisonTool } from "./registerFunctionComparisonTool.js";
@@ -45,13 +44,6 @@ const recordProcessResidualUnknowns = (session, scenario, evidence, residuals) =
45
44
  }
46
45
  return ok(null);
47
46
  };
48
- const processEvidenceParameters = (scenario) => ({
49
- executable_name: scenario.executable.split("/").at(-1) ?? scenario.executable,
50
- argument_count: scenario.arguments.length,
51
- event_count: scenario.events.length,
52
- filesystem_root_count: scenario.filesystem_roots.length,
53
- normalization: scenario.normalization,
54
- });
55
47
  const registerProcessTools = ({ server, session, logger, processPolicy, captureContract, }) => {
56
48
  server.registerTool(captureContract.name, {
57
49
  description: captureContract.description,
@@ -63,21 +55,7 @@ const registerProcessTools = ({ server, session, logger, processPolicy, captureC
63
55
  const captured = await logToolExecution(logger, captureContract.name, () => captureProcessScenario(scenario, processPolicy, context.mcpReq.signal));
64
56
  if (!captured.ok)
65
57
  return toCallToolResult(captured, captureContract);
66
- const evidence = createEvidence(undefined, PROCESS_PROVIDER, {
67
- predicateType: "rea.process-capture/v2",
68
- operation: captureContract.name,
69
- parameters: processEvidenceParameters(scenario),
70
- result: jsonValueSchema.parse(captured.value),
71
- confidence: "observed",
72
- authority: "controlled-replay",
73
- environment: {
74
- id: `${process.platform}-${process.arch}`,
75
- platform: process.platform,
76
- architecture: process.arch,
77
- isolation: "process",
78
- },
79
- limitations: captured.value.limitations,
80
- });
58
+ const evidence = createProcessCaptureEvidence(scenario, captured.value);
81
59
  const recorded = session.recordEvidence(evidence);
82
60
  if (!recorded.ok)
83
61
  return toCallToolResult(recorded, captureContract);
@@ -14,12 +14,7 @@ export const DENY_EVIDENCE_FILE_POLICY = {
14
14
  maxStringLength: 1024 * 1024,
15
15
  maxNodes: 1_000_000,
16
16
  };
17
- /** Provider identity for controlled process-capture evidence. */
18
- export const PROCESS_PROVIDER = {
19
- id: "rea-process",
20
- name: "REA deterministic process harness",
21
- version: "1",
22
- };
17
+ export { PROCESS_PROVIDER } from "../application/ProcessEvidence.js";
23
18
  /** Provider identity for deterministic cross-artifact comparisons. */
24
19
  export const ARTIFACT_COMPARISON_PROVIDER = {
25
20
  id: "rea-artifact-comparison",
package/install.sh CHANGED
@@ -3,196 +3,111 @@ set -euo pipefail
3
3
 
4
4
  PACKAGE="rea-agents"
5
5
  REPOSITORY="morluto/rea"
6
- NODE_FORMULA="node@24"
7
- NODE_VERSION="24.18.0"
8
- temporary_directory=""
9
- node_destination=""
10
- node_stage=""
11
- node_backup=""
12
- node_version=""
13
-
14
- cleanup() {
15
- if [[ -n "$temporary_directory" && -d "$temporary_directory" ]]; then
16
- rm -rf -- "$temporary_directory"
17
- fi
18
- if [[ -n "$node_stage" && -e "$node_stage" ]]; then
19
- rm -rf -- "$node_stage"
20
- fi
21
- if [[ -n "$node_backup" && -e "$node_backup" && ! -e "$node_destination" ]]; then
22
- mv -- "$node_backup" "$node_destination"
23
- fi
24
- }
25
- trap cleanup EXIT HUP INT TERM
6
+ version="${REA_VERSION:-}"
7
+ dry_run=false
8
+ start_setup=true
9
+ verbose=false
26
10
 
27
11
  fail() {
28
12
  printf 'REA installation failed: %s\n' "$1" >&2
29
13
  exit 1
30
14
  }
31
15
 
32
- version_at_least() {
33
- local actual="$1" required_major="$2" required_minor="$3"
34
- local major minor
35
- major="${actual%%.*}"
36
- minor="${actual#*.}"
37
- minor="${minor%%.*}"
38
- [[ "$major" =~ ^[0-9]+$ && "$minor" =~ ^[0-9]+$ ]] || return 1
39
- ((major > required_major || (major == required_major && minor >= required_minor)))
16
+ usage() {
17
+ cat <<'EOF'
18
+ Usage: install.sh [--version VERSION] [--dry-run] [--no-setup] [--no-prompt] [--verbose]
19
+
20
+ Installs only the REA CLI. Run `rea setup` separately to configure Hopper and coding agents.
21
+ EOF
40
22
  }
41
23
 
42
- platform="$(uname -s)"
43
- if [[ "$platform" == "Darwin" ]]; then
44
- macos_version="$(sw_vers -productVersion 2>/dev/null)" || fail "could not determine the macOS version."
45
- macos_major="${macos_version%%.*}"
46
- [[ "$macos_major" =~ ^[0-9]+$ && "$macos_major" -ge 12 ]] || fail "macOS 12 or newer is required."
47
- elif [[ "$platform" == "Linux" ]]; then
48
- [[ "$(uname -m)" == "x86_64" || "$(uname -m)" == "aarch64" ]] || fail "64-bit x86 or ARM Linux is required."
49
- [[ -r /etc/os-release ]] || fail "could not identify the Linux distribution."
50
- linux_id="$(sed -n 's/^ID=//p' /etc/os-release | tr -d '"')"
51
- linux_version="$(sed -n 's/^VERSION_ID=//p' /etc/os-release | tr -d '"')"
52
- case "$linux_id" in
53
- ubuntu) [[ "${linux_version%%.*}" -ge 24 ]] || fail "Ubuntu 24.04 or newer is required." ;;
54
- fedora) [[ "${linux_version%%.*}" -ge 41 ]] || fail "Fedora 41 or newer is required." ;;
55
- arch) ;;
56
- *) fail "supported Linux distributions are Ubuntu 24.04+, Fedora 41+, and Arch Linux." ;;
24
+ while (($# > 0)); do
25
+ case "$1" in
26
+ --version)
27
+ (($# >= 2)) || fail "--version requires a value."
28
+ version="$2"
29
+ shift 2
30
+ ;;
31
+ --dry-run)
32
+ dry_run=true
33
+ shift
34
+ ;;
35
+ --no-setup|--no-prompt)
36
+ start_setup=false
37
+ shift
38
+ ;;
39
+ --verbose)
40
+ verbose=true
41
+ shift
42
+ ;;
43
+ --help|-h)
44
+ usage
45
+ exit 0
46
+ ;;
47
+ *) fail "unknown option: $1" ;;
57
48
  esac
58
- else
59
- fail "REA supports macOS and selected 64-bit Linux distributions."
60
- fi
61
- command -v curl >/dev/null 2>&1 || fail "curl is required."
62
- command -v shasum >/dev/null 2>&1 || command -v sha256sum >/dev/null 2>&1 || fail "shasum or sha256sum is required."
49
+ done
63
50
 
64
- if [[ "$platform" == "Darwin" ]] && ! command -v brew >/dev/null 2>&1; then
65
- printf 'Installing Homebrew prerequisite...\n'
66
- NONINTERACTIVE=1 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" || fail "Homebrew installation did not complete."
67
- if [[ -x /opt/homebrew/bin/brew ]]; then
68
- PATH="/opt/homebrew/bin:$PATH"
69
- elif [[ -x /usr/local/bin/brew ]]; then
70
- PATH="/usr/local/bin:$PATH"
71
- fi
72
- fi
73
- if [[ "$platform" == "Darwin" ]]; then
74
- command -v brew >/dev/null 2>&1 || fail "Homebrew is not available after installation."
51
+ if [[ "$verbose" == true ]]; then
52
+ set -x
75
53
  fi
76
54
 
77
- node_compatible=false
78
- if command -v node >/dev/null 2>&1; then
79
- node_version="$(node -p 'process.versions.node' 2>/dev/null || true)"
80
- fi
81
- if version_at_least "$node_version" 24 18 && [[ "$node_version" =~ ^24\. ]]; then
82
- node_compatible=true
83
- fi
84
- if [[ "$node_compatible" != true ]]; then
85
- if [[ "$platform" == "Darwin" ]]; then
86
- brew install "$NODE_FORMULA" || fail "Node.js 24 installation failed."
87
- node_prefix="$(brew --prefix "$NODE_FORMULA")" || fail "could not locate Node.js 24."
88
- PATH="$node_prefix/bin:$PATH"
89
- else
90
- machine="$(uname -m)"
91
- node_arch="$([[ "$machine" == "x86_64" ]] && printf 'x64' || printf 'arm64')"
92
- node_archive="node-v${NODE_VERSION}-linux-${node_arch}.tar.xz"
93
- node_base="https://nodejs.org/dist/v${NODE_VERSION}"
94
- temporary_directory="$(mktemp -d "${TMPDIR:-/tmp}/rea-install.XXXXXX")" || fail "could not create a temporary directory."
95
- curl -fsSL "$node_base/$node_archive" -o "$temporary_directory/$node_archive" || fail "Node.js download failed."
96
- curl -fsSL "$node_base/SHASUMS256.txt" -o "$temporary_directory/SHASUMS256.txt" || fail "Node.js checksum download failed."
97
- expected_checksum="$(awk -v archive="$node_archive" '$2 == archive { print $1 }' "$temporary_directory/SHASUMS256.txt")"
98
- [[ "$expected_checksum" =~ ^[a-f0-9]{64}$ ]] || fail "Node.js checksum metadata was malformed."
99
- if command -v shasum >/dev/null 2>&1; then
100
- actual_checksum="$(shasum -a 256 "$temporary_directory/$node_archive" | awk '{ print $1 }')"
101
- else
102
- actual_checksum="$(sha256sum "$temporary_directory/$node_archive" | awk '{ print $1 }')"
103
- fi
104
- [[ "$actual_checksum" == "$expected_checksum" ]] || fail "Node.js checksum verification failed."
105
- node_destination="$HOME/.local/share/rea/node"
106
- node_stage="$HOME/.local/share/rea/node.new.$$"
107
- node_backup="$HOME/.local/share/rea/node.backup.$$"
108
- [[ ! -L "$node_destination" ]] || fail "refusing to replace a symlinked Node.js installation."
109
- mkdir -p "$HOME/.local/share/rea"
110
- rm -rf -- "$node_stage" "$node_backup"
111
- mkdir "$node_stage"
112
- tar -xJf "$temporary_directory/$node_archive" --strip-components=1 -C "$node_stage" || fail "Node.js extraction failed."
113
- if [[ -e "$node_destination" ]]; then
114
- mv -- "$node_destination" "$node_backup"
115
- fi
116
- if ! mv -- "$node_stage" "$node_destination"; then
117
- [[ ! -e "$node_backup" ]] || mv -- "$node_backup" "$node_destination"
118
- fail "Node.js installation replacement failed."
119
- fi
120
- node_stage=""
121
- rm -rf -- "$node_backup"
122
- node_backup=""
123
- PATH="$node_destination/bin:$PATH"
124
- fi
125
- fi
55
+ platform="$(uname -s)"
56
+ [[ "$platform" == "Darwin" || "$platform" == "Linux" ]] || fail "REA supports macOS and selected 64-bit Linux distributions."
57
+ command -v curl >/dev/null 2>&1 || fail "curl is required."
58
+ command -v node >/dev/null 2>&1 || fail "Node.js 22.19+ or 24.11+ is required; install it from https://nodejs.org and retry."
59
+ command -v npm >/dev/null 2>&1 || fail "npm is required; install it with Node.js and retry."
60
+
126
61
  node_version="$(node -p 'process.versions.node' 2>/dev/null || true)"
127
- if ! command -v node >/dev/null 2>&1 || ! version_at_least "$node_version" 24 18 || [[ ! "$node_version" =~ ^24\. ]]; then
128
- fail "Node.js 24.18 or newer is not active on PATH."
129
- fi
130
- command -v npm >/dev/null 2>&1 || fail "npm is missing from the Node.js installation."
131
- npm_version="$(npm --version)"
132
- if [[ ! "$npm_version" =~ ^11\. ]] || ! version_at_least "$npm_version" 11 16; then
133
- if [[ "$platform" == "Linux" ]]; then
134
- npm install --global --prefix "$HOME/.local" npm@11 || fail "npm 11 installation failed."
135
- else
136
- npm install --global npm@11 || fail "npm 11 installation failed."
137
- fi
138
- fi
139
- npm_version="$(npm --version)"
140
- if [[ ! "$npm_version" =~ ^11\. ]] || ! version_at_least "$npm_version" 11 16; then
141
- fail "npm 11.16 or newer is not active on PATH."
62
+ IFS=. read -r node_major node_minor _node_patch <<<"$node_version"
63
+ [[ "$node_major" =~ ^[0-9]+$ && "$node_minor" =~ ^[0-9]+$ ]] || fail "could not determine the active Node.js version."
64
+ if ! ((node_major == 22 && node_minor >= 19 || node_major >= 24)); then
65
+ fail "Node.js $node_version is unsupported; use Node.js 22.19+ or 24.11+."
142
66
  fi
143
- if [[ -n "$temporary_directory" ]]; then
144
- rm -rf -- "$temporary_directory"
145
- temporary_directory=""
67
+ if ((node_major == 24 && node_minor < 11)); then
68
+ fail "Node.js $node_version is unsupported; use Node.js 24.11 or newer."
146
69
  fi
147
70
 
148
- if [[ -n "${REA_VERSION:-}" ]]; then
149
- version="$REA_VERSION"
150
- [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]] || fail "REA_VERSION must be an exact semantic version."
71
+ if [[ -n "$version" ]]; then
72
+ [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]] || fail "version must be an exact semantic version."
151
73
  else
152
- release_json="$(curl -fsSL -H 'Accept: application/vnd.github+json' "https://api.github.com/repos/$REPOSITORY/releases/latest")" || fail "could not resolve the latest GitHub release."
74
+ release_json="$(curl -fsSL --proto '=https' --tlsv1.2 --retry 3 -H 'Accept: application/vnd.github+json' "https://api.github.com/repos/$REPOSITORY/releases/latest")" || fail "could not resolve the latest GitHub release."
153
75
  tag="$(printf '%s' "$release_json" | node -e 'let s="";process.stdin.on("data",c=>s+=c).on("end",()=>{const v=JSON.parse(s).tag_name;if(typeof v!=="string")process.exit(1);process.stdout.write(v)})')" || fail "GitHub returned malformed release metadata."
154
76
  [[ "$tag" =~ ^rea-agents-([0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?)$ ]] || fail "latest release tag is not a valid rea-agents release."
155
77
  version="${BASH_REMATCH[1]}"
156
78
  fi
157
79
 
158
- printf 'Installing %s@%s...\n' "$PACKAGE" "$version"
80
+ prefix_args=()
159
81
  if [[ "$platform" == "Linux" ]]; then
160
- mkdir -p "$HOME/.local/bin"
161
- PATH="$HOME/.local/bin:$PATH"
162
- npm install --global --prefix "$HOME/.local" "$PACKAGE@$version" || fail "npm package installation failed."
82
+ prefix_args=(--prefix "$HOME/.local")
83
+ install_bin="$HOME/.local/bin/rea"
163
84
  else
164
- npm install --global "$PACKAGE@$version" || fail "npm package installation failed."
85
+ npm_prefix="$(npm prefix --global)" || fail "could not determine the npm global prefix."
86
+ install_bin="$npm_prefix/bin/rea"
165
87
  fi
166
- command -v rea >/dev/null 2>&1 || fail "the rea command is not available after installation."
167
- installed_version="$(rea --version 2>/dev/null | tr -d '[:space:]')" || fail "could not read the installed REA version."
168
- [[ "$installed_version" == "$version" ]] || fail "installed version $installed_version does not match $version."
169
88
 
170
- temporary_directory="$(mktemp -d "${TMPDIR:-/tmp}/rea-install.XXXXXX")" || fail "could not create a temporary directory."
171
- setup_output="$temporary_directory/setup.json"
172
- doctor_output="$temporary_directory/doctor.json"
173
- rea setup --yes --json >"$setup_output" || fail "REA setup command failed."
174
- rea doctor --json >"$doctor_output" || fail "REA doctor command failed."
175
- result="$(node - "$setup_output" "$doctor_output" <<'NODE'
176
- const fs = require("node:fs");
177
- const [setupPath, doctorPath] = process.argv.slice(2);
178
- try {
179
- const setup = JSON.parse(fs.readFileSync(setupPath, "utf8"));
180
- const doctor = JSON.parse(fs.readFileSync(doctorPath, "utf8"));
181
- if (!Array.isArray(doctor.checks) || typeof doctor.healthy !== "boolean") process.exit(2);
182
- if (setup.status !== "ready" && setup.status !== "needs_human") process.exit(2);
183
- const activation = setup.actions.includes("installed_hopper");
184
- if (!doctor.healthy && !activation) process.exit(3);
185
- process.stdout.write(activation ? "activation" : "ready");
186
- } catch { process.exit(2); }
187
- NODE
188
- )" || fail "setup or doctor reported an unhealthy required component or malformed output."
89
+ printf 'REA install plan\n'
90
+ printf ' Version: %s\n' "$version"
91
+ printf ' Runtime: Node.js %s\n' "$node_version"
92
+ printf ' Command: npm install --global %s@%s\n' "$PACKAGE" "$version"
93
+ printf ' Binary: %s\n' "$install_bin"
94
+ printf ' Setup: %s\n' "$([[ "$start_setup" == true ]] && printf 'start when a terminal is available' || printf 'skipped')"
189
95
 
190
- if [[ "$result" == "activation" ]]; then
191
- printf 'REA %s is installed and coding agents are configured.\n' "$version"
192
- printf 'One user action remains: open Hopper, complete its activation, then run: rea doctor --json\n'
193
- else
194
- printf 'REA %s is installed and ready. Run rea doctor --json at any time.\n' "$version"
96
+ if [[ "$dry_run" == true ]]; then
97
+ printf 'Dry run complete; no changes made.\n'
98
+ exit 0
195
99
  fi
196
- if [[ "$platform" == "Linux" ]]; then
197
- printf 'Add %s to PATH to run rea from future shells.\n' "$HOME/.local/bin"
100
+
101
+ printf 'Installing %s@%s...\n' "$PACKAGE" "$version"
102
+ npm install --global "${prefix_args[@]}" "$PACKAGE@$version" || fail "npm package installation failed."
103
+ [[ -x "$install_bin" ]] || fail "the rea command was not installed at $install_bin."
104
+ installed_version="$("$install_bin" --version 2>/dev/null | tr -d '[:space:]')" || fail "could not read the installed REA version."
105
+ [[ "$installed_version" == "$version" ]] || fail "installed version $installed_version does not match $version."
106
+
107
+ printf 'REA %s is installed.\n' "$version"
108
+ if [[ "$start_setup" == true && -r /dev/tty && -w /dev/tty && -t 1 ]]; then
109
+ printf 'Starting REA setup...\n'
110
+ "$install_bin" setup </dev/tty >/dev/tty
111
+ else
112
+ printf 'Run %s setup to configure Hopper and coding agents.\n' "$install_bin"
198
113
  fi
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rea-agents",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "Reverse engineer anything from your terminal or coding agent with one CLI and MCP server.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -33,8 +33,7 @@
33
33
  "rea": "scripts/rea.mjs"
34
34
  },
35
35
  "engines": {
36
- "node": ">=24.18.0 <25",
37
- "npm": ">=11.16.0 <12"
36
+ "node": "^22.19.0 || >=24.11.0"
38
37
  },
39
38
  "packageManager": "npm@11.16.0",
40
39
  "files": [
@@ -42,6 +41,7 @@
42
41
  "install.sh",
43
42
  "bridge/hopper_bridge.py",
44
43
  "scripts/rea.mjs",
44
+ "scripts/prepare-node-pty.mjs",
45
45
  "scripts/rebuild-native.mjs",
46
46
  "skills",
47
47
  "README.md",
@@ -60,6 +60,7 @@
60
60
  "scan:todos": "node scripts/scan-todos.mjs",
61
61
  "start": "node dist/main.js",
62
62
  "pretest": "npm run build",
63
+ "postinstall": "node scripts/prepare-node-pty.mjs",
63
64
  "test": "vitest run",
64
65
  "pretest:coverage": "npm run build",
65
66
  "test:coverage": "vitest run --coverage.enabled --coverage.provider=v8",
@@ -87,6 +88,8 @@
87
88
  "@babel/types": "8.0.4",
88
89
  "@electron/asar": "4.2.0",
89
90
  "@modelcontextprotocol/server": "2.0.0-beta.3",
91
+ "@xterm/addon-serialize": "0.14.0",
92
+ "@xterm/headless": "6.0.0",
90
93
  "@zip.js/zip.js": "2.8.26",
91
94
  "canonicalize": "3.0.0",
92
95
  "diff": "9.0.0",
@@ -99,7 +102,7 @@
99
102
  "pino": "9.14.0",
100
103
  "plist": "5.0.0",
101
104
  "smol-toml": "^1.7.0",
102
- "write-file-atomic": "8.0.0",
105
+ "write-file-atomic": "7.0.0",
103
106
  "ws": "8.21.0",
104
107
  "zod": "4.4.3"
105
108
  },
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { chmod, lstat } from "node:fs/promises";
4
+ import { createRequire } from "node:module";
5
+ import { dirname, join } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ if (process.platform !== "win32") {
9
+ const require = createRequire(import.meta.url);
10
+ const packageRoot = dirname(
11
+ require.resolve("node-pty/package.json", {
12
+ paths: [dirname(fileURLToPath(import.meta.url))],
13
+ }),
14
+ );
15
+ const helperPath = join(
16
+ packageRoot,
17
+ "prebuilds",
18
+ `${process.platform}-${process.arch}`,
19
+ "spawn-helper",
20
+ );
21
+
22
+ try {
23
+ const helper = await lstat(helperPath);
24
+ if (!helper.isFile() || helper.isSymbolicLink()) {
25
+ throw new Error("node-pty spawn helper is not a regular file");
26
+ }
27
+ await chmod(helperPath, helper.mode | 0o111);
28
+ } catch (error) {
29
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
30
+ // Source builds place their correctly-modeled helper under build/Release.
31
+ } else {
32
+ throw error;
33
+ }
34
+ }
35
+ }
@@ -2,7 +2,7 @@
2
2
  name: rea-analysis
3
3
  description: Reverse engineer apps with REA. Explore how features work, then build a version tailored to your project.
4
4
  metadata:
5
- version: "10"
5
+ version: "11"
6
6
  tool_count: 68
7
7
  ---
8
8
 
@@ -36,13 +36,19 @@ REA accepts a `.app` bundle directly. Do not expose its internal `Contents/MacOS
36
36
 
37
37
  Briefly tell the user what you will investigate. Open the app with `open_binary`, begin with `binary_overview`, and narrow the investigation around the requested feature. Use decompilation, strings, names, callers, callees, and cross-references as needed.
38
38
 
39
- For applications, ZIP/APK/IPA packages, or Electron ASAR archives, call
39
+ For applications, ZIP/APK/IPA packages, Electron ASAR archives, or DMGs, call
40
40
  `inventory_artifact` before extraction. Follow its deterministic occurrence
41
41
  pages and cite graph manifest IDs. `extract_artifact` requires explicit user
42
42
  approval, an absent absolute output root, and selected occurrence IDs; never
43
43
  extract every entry implicitly. Symlinks and encrypted entries are inventory
44
- facts, not extractable files. Nested containers require a separate deliberate
45
- inventory step.
44
+ facts, not extractable files. Inventory traverses discovered ASARs within the
45
+ same graph and shared limits, including ASARs inside an approved mounted DMG.
46
+
47
+ Native DMG traversal is macOS-only, read-only, and disabled by default. Use it
48
+ only when the user approves that inventory call with
49
+ `native_mount_approved: true` and the operator has separately enabled
50
+ `REA_ARTIFACT_NATIVE_MOUNT_ENABLED=true`. Without both gates, retain the DMG
51
+ root-hash-only result. Never imply that approval changes extraction authority.
46
52
 
47
53
  Explain conclusions in plain language. Point to the relevant decompiled code, strings, names, and connections so the user can see how the explanation was reached. Do not claim to recover original source code or automatically clone an application.
48
54
 
@@ -57,6 +63,15 @@ requires per-call approval plus operator policy, and uses host networking only
57
63
  when the operator explicitly permits it. It is behavioral evidence, not a
58
64
  security sandbox.
59
65
 
66
+ `capture_process_scenario` produces Process Capture v4 only. If a boundary
67
+ reports Process Capture v3, tell the user or calling agent to rerun the original
68
+ scenario; v3 cannot be upgraded because it lacks required manifest and
69
+ settlement evidence. Treat the v4 manifest commitments as compatibility and
70
+ provenance evidence, and distinguish root exit from descendant settlement.
71
+ When comparing stored captures, set `max_capture_age_ms` when the task requires
72
+ fresh behavioral evidence. Different scenario and executable digests are
73
+ allowed, but schema and comparison-contract digests must be compatible.
74
+
60
75
  Do not let unanswered questions disappear. Use `record_unknown` only with
61
76
  explicit approval, attach supporting and contradicting evidence IDs, and record
62
77
  the authority/environment still required. Use `update_unknown` with the current