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.
- package/README.md +49 -18
- package/dist/application/ArtifactInventory.js +80 -44
- package/dist/application/CommandShimReplay.js +136 -0
- package/dist/application/Doctor.js +4 -4
- package/dist/application/EvidenceBundleFiles.js +13 -4
- package/dist/application/MacHopper.js +175 -0
- package/dist/application/ProcessCaptureAuthority.js +25 -0
- package/dist/application/ProcessCaptureCapability.js +23 -0
- package/dist/application/ProcessCaptureError.js +5 -0
- package/dist/application/ProcessCaptureLifecycle.js +269 -0
- package/dist/application/ProcessCheckpoints.js +129 -0
- package/dist/application/ProcessCli.js +61 -0
- package/dist/application/ProcessEvidence.js +38 -0
- package/dist/application/ProcessHarness.js +252 -272
- package/dist/application/ProcessNormalization.js +10 -1
- package/dist/application/ProcessOwnership.js +39 -1
- package/dist/application/ProcessSampling.js +39 -23
- package/dist/application/Setup.js +104 -100
- package/dist/application/SetupSkill.js +44 -0
- package/dist/application/TerminalRenderer.js +92 -0
- package/dist/application/runtime.js +1 -1
- package/dist/artifacts/ArtifactProvider.js +18 -6
- package/dist/artifacts/ArtifactReader.js +3 -1
- package/dist/artifacts/AsarArtifactReader.js +8 -1
- package/dist/artifacts/DirectoryArtifactReader.js +1 -0
- package/dist/artifacts/MachOSliceArtifactReader.js +1 -0
- package/dist/artifacts/NativeDmgArtifactReader.js +151 -0
- package/dist/artifacts/ZipArtifactReader.js +1 -0
- package/dist/cli.js +60 -26
- package/dist/cliProcessCommands.js +19 -0
- package/dist/config.js +2 -0
- package/dist/contracts/artifactToolContracts.js +1 -0
- package/dist/contracts/investigationExamples.js +3 -3
- package/dist/contracts/processCaptureExample.js +50 -7
- package/dist/contracts/toolContracts.js +3 -2
- package/dist/domain/changedBehavior.js +2 -2
- package/dist/domain/errors.js +11 -3
- package/dist/domain/processCapture.js +99 -293
- package/dist/domain/processCaptureValidation.js +124 -0
- package/dist/domain/processComparison.js +176 -32
- package/dist/domain/processScenario.js +370 -0
- package/dist/domain/reconstructionVerification.js +3 -3
- package/dist/domain/runtimeVersion.js +8 -0
- package/dist/domain/staticRuntimeCorrelation.js +3 -2
- package/dist/identity.js +1 -0
- package/dist/server/registerProcessComparisonTool.js +28 -8
- package/dist/server/registerSessionTools.js +3 -25
- package/dist/server/sessionToolPolicies.js +1 -6
- package/install.sh +79 -164
- package/package.json +7 -4
- package/scripts/prepare-node-pty.mjs +35 -0
- 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
|
-
|
|
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/
|
|
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
|
-
|
|
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/
|
|
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
|
-
|
|
63
|
-
canonicalJson(
|
|
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,
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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 [[ "$
|
|
65
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
|
144
|
-
|
|
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 "$
|
|
149
|
-
|
|
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
|
-
|
|
80
|
+
prefix_args=()
|
|
159
81
|
if [[ "$platform" == "Linux" ]]; then
|
|
160
|
-
|
|
161
|
-
|
|
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
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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 [[ "$
|
|
191
|
-
printf '
|
|
192
|
-
|
|
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
|
-
|
|
197
|
-
|
|
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.
|
|
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.
|
|
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": "
|
|
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: "
|
|
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,
|
|
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.
|
|
45
|
-
|
|
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
|