rea-agents 1.2.0 → 1.4.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 +95 -27
- package/bridge/hopper_bridge.py +172 -14
- package/dist/application/AnalysisSnapshotCache.js +131 -0
- package/dist/application/AnalysisSnapshotFiles.js +31 -0
- package/dist/application/ArtifactGraphConstruction.js +1 -1
- package/dist/application/ArtifactInventory.js +100 -24
- package/dist/application/AuthorizedArtifactInventory.js +16 -0
- package/dist/application/BinarySession.js +149 -36
- package/dist/application/BinarySessionPort.js +1 -0
- package/dist/application/BoundedJsonFiles.js +109 -0
- package/dist/application/CapabilityInventory.js +127 -0
- package/dist/application/ClientRegistrationStatus.js +69 -0
- package/dist/application/CrossVersionInventory.js +109 -0
- package/dist/application/CrossVersionInvestigation.js +358 -0
- package/dist/application/DirectAnalysis.js +205 -24
- package/dist/application/Doctor.js +104 -3
- package/dist/application/EvidenceBundleFiles.js +15 -108
- package/dist/application/EvidenceLedger.js +3 -2
- package/dist/application/InvestigationProviders.js +48 -0
- package/dist/application/InvestigationWorkspaceStore.js +214 -0
- package/dist/application/LinuxHopper.js +72 -48
- package/dist/application/PermissionAuthority.js +183 -0
- package/dist/application/PermissionConfiguration.js +26 -0
- package/dist/application/ProcessCaptureAuthority.js +2 -2
- package/dist/application/ProcessCaptureError.js +25 -0
- package/dist/application/ProcessCaptureLifecycle.js +22 -2
- package/dist/application/ProcessCli.js +111 -31
- package/dist/application/ProcessHarness.js +6 -3
- package/dist/application/ProgressReporter.js +30 -0
- package/dist/application/ProjectPermissionStore.js +110 -0
- package/dist/application/ReferenceSourceImportEntries.js +18 -3
- package/dist/application/ReferenceSourceImportTypes.js +32 -0
- package/dist/application/Setup.js +35 -101
- package/dist/application/SetupClients.js +19 -0
- package/dist/application/SetupInstallFailure.js +53 -0
- package/dist/application/SetupPlan.js +31 -0
- package/dist/application/SupportedClients.js +41 -0
- package/dist/application/Uninstall.js +34 -24
- package/dist/application/UnknownEvidence.js +33 -0
- package/dist/application/Upgrade.js +3 -1
- package/dist/application/runtime.js +2 -2
- package/dist/artifacts/ArtifactProvider.js +15 -8
- package/dist/catalogIdentity.js +121 -0
- package/dist/cli.js +76 -13
- package/dist/cliEvidenceCommands.js +52 -12
- package/dist/cliInvestigationCommands.js +137 -0
- package/dist/cliLogging.js +20 -3
- package/dist/cliOutput.js +41 -0
- package/dist/cliPolicyCommands.js +115 -0
- package/dist/config.js +102 -27
- package/dist/contracts/artifactToolContracts.js +14 -1
- package/dist/contracts/errorSchemas.js +98 -0
- package/dist/contracts/promptContracts.js +256 -0
- package/dist/contracts/sessionLifecycleInputs.js +11 -0
- package/dist/contracts/toolContractTypes.js +1 -0
- package/dist/contracts/toolContracts.js +20 -7
- package/dist/contracts/toolOutputSchemas.js +70 -9
- package/dist/domain/analysisSnapshot.js +149 -0
- package/dist/domain/artifactComparison.js +38 -11
- package/dist/domain/artifactGraph.js +25 -1
- package/dist/domain/artifactInventoryEvidence.js +18 -0
- package/dist/domain/changedBehavior.js +35 -10
- package/dist/domain/errors.js +384 -32
- package/dist/domain/evidence.js +4 -2
- package/dist/domain/evidenceBundle.js +28 -0
- package/dist/domain/hopperStartupFailure.js +55 -0
- package/dist/domain/investigationWorkspace.js +341 -0
- package/dist/domain/jsonValue.js +2 -0
- package/dist/domain/nativeInspection.js +3 -2
- package/dist/domain/permissionPolicy.js +157 -0
- package/dist/domain/processCapture.js +5 -4
- package/dist/domain/processComparison.js +6 -4
- package/dist/domain/reconstructionVerification.js +1 -1
- package/dist/domain/reconstructionVerificationSchemas.js +1 -0
- package/dist/domain/staticRuntimeCorrelation.js +5 -1
- package/dist/generatedPackageMetadata.js +9 -0
- package/dist/hopper/BridgeLauncher.js +51 -20
- package/dist/hopper/HopperClient.js +45 -3
- package/dist/hopper/HopperProvider.js +1 -0
- package/dist/identity.js +10 -3
- package/dist/logger.js +2 -2
- package/dist/main.js +139 -28
- package/dist/server/createServer.js +54 -5
- package/dist/server/mcpProgress.js +23 -0
- package/dist/server/promptCompletion.js +144 -0
- package/dist/server/registerArtifactComparisonTool.js +6 -2
- package/dist/server/registerBundleComparisonTool.js +6 -2
- package/dist/server/registerEnhancedTools.js +20 -7
- package/dist/server/registerEvidenceResources.js +302 -0
- package/dist/server/registerEvidenceTools.js +59 -8
- package/dist/server/registerFunctionComparisonTool.js +7 -3
- package/dist/server/registerInvestigationTools.js +111 -18
- package/dist/server/registerOfficialTools.js +25 -14
- package/dist/server/registerProcessComparisonTool.js +8 -10
- package/dist/server/registerPrompts.js +67 -0
- package/dist/server/registerSessionStatusTool.js +52 -0
- package/dist/server/registerSessionTools.js +178 -22
- package/dist/server/runDerivedOperation.js +35 -0
- package/dist/server/sessionEvidence.js +2 -8
- package/dist/server/sessionToolPolicies.js +1 -42
- package/dist/server/toolResult.js +34 -6
- package/dist/serverIdentity.js +70 -0
- package/install.sh +11 -11
- package/package.json +10 -6
- package/scripts/hopper-demo-x11.py +349 -0
- package/scripts/rea.mjs +6 -3
- package/skills/rea-analysis/SKILL.md +9 -2
|
@@ -5,7 +5,14 @@ import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
|
5
5
|
import writeFileAtomic from "write-file-atomic";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import { PRODUCT_IDENTITY } from "../identity.js";
|
|
8
|
-
import { supportedClients } from "./
|
|
8
|
+
import { supportedClients } from "./SupportedClients.js";
|
|
9
|
+
const systemFileSystem = {
|
|
10
|
+
readText: (path) => readFile(path, "utf8"),
|
|
11
|
+
copy: (source, destination) => copyFile(source, destination),
|
|
12
|
+
writeText: (path, contents) => writeFileAtomic(path, contents, { encoding: "utf8" }),
|
|
13
|
+
stat: (path) => lstat(path),
|
|
14
|
+
remove: (path) => rm(path, { recursive: true }),
|
|
15
|
+
};
|
|
9
16
|
/** Remove only REA-owned registrations and managed files, optionally including local state. */
|
|
10
17
|
export const runUninstall = async (purgeData, host = systemUninstallHost()) => {
|
|
11
18
|
const items = [];
|
|
@@ -27,26 +34,26 @@ export const runUninstall = async (purgeData, host = systemUninstallHost()) => {
|
|
|
27
34
|
};
|
|
28
35
|
};
|
|
29
36
|
/** Create uninstall effects contained to detected client configs and REA-owned paths. */
|
|
30
|
-
export const systemUninstallHost = (home = homedir()) => ({
|
|
37
|
+
export const systemUninstallHost = (home = homedir(), fileSystem = systemFileSystem) => ({
|
|
31
38
|
clients: () => Promise.resolve(supportedClients(home)),
|
|
32
|
-
removeClient,
|
|
33
|
-
removeSkill: () => removeManagedPath(join(home, ".agents/skills", PRODUCT_IDENTITY.skillName), "skill"),
|
|
39
|
+
removeClient: (client) => removeClient(client, fileSystem),
|
|
40
|
+
removeSkill: () => removeManagedPath(join(home, ".agents/skills", PRODUCT_IDENTITY.skillName), "skill", fileSystem),
|
|
34
41
|
purgeData: async () => [
|
|
35
|
-
await removeManagedPath(join(home, ".rea/cache"), "cache"),
|
|
36
|
-
await removeManagedPath(join(home, ".rea/state"), "state"),
|
|
42
|
+
await removeManagedPath(join(home, ".rea/cache"), "cache", fileSystem),
|
|
43
|
+
await removeManagedPath(join(home, ".rea/state"), "state", fileSystem),
|
|
37
44
|
],
|
|
38
45
|
});
|
|
39
|
-
const removeClient = async (client) => {
|
|
46
|
+
const removeClient = async (client, fileSystem) => {
|
|
40
47
|
if (client.format === "unsupported")
|
|
41
48
|
return item(client.name, "skipped", "This client has no documented local MCP configuration boundary.");
|
|
42
49
|
let original;
|
|
43
50
|
try {
|
|
44
|
-
original = await
|
|
51
|
+
original = await fileSystem.readText(client.configPath);
|
|
45
52
|
}
|
|
46
53
|
catch (cause) {
|
|
47
54
|
return isMissing(cause)
|
|
48
55
|
? item(client.name, "skipped", "Configuration does not exist.")
|
|
49
|
-
: item(client.name, "failed", "Configuration could not be read.");
|
|
56
|
+
: item(client.name, "failed", "Configuration could not be read. Check file permissions, then rerun uninstall.");
|
|
50
57
|
}
|
|
51
58
|
let document;
|
|
52
59
|
let key;
|
|
@@ -57,7 +64,7 @@ const removeClient = async (client) => {
|
|
|
57
64
|
servers = optionalObject(document[key]);
|
|
58
65
|
}
|
|
59
66
|
catch {
|
|
60
|
-
return item(client.name, "failed", "
|
|
67
|
+
return item(client.name, "failed", `Configuration is not valid ${client.format === "toml" ? "TOML" : "JSON"} and was not changed. Repair it, then rerun uninstall.`);
|
|
61
68
|
}
|
|
62
69
|
const registration = servers[PRODUCT_IDENTITY.mcpServerKey];
|
|
63
70
|
if (registration === undefined)
|
|
@@ -69,28 +76,31 @@ const removeClient = async (client) => {
|
|
|
69
76
|
document[key] = remaining;
|
|
70
77
|
const backupPath = `${client.configPath}.rea.backup`;
|
|
71
78
|
try {
|
|
72
|
-
await
|
|
79
|
+
await fileSystem.copy(client.configPath, backupPath);
|
|
73
80
|
}
|
|
74
81
|
catch {
|
|
75
|
-
return item(client.name, "failed", "Configuration
|
|
82
|
+
return item(client.name, "failed", "Configuration could not be backed up, so no change was made. Check file permissions, then rerun uninstall.");
|
|
76
83
|
}
|
|
77
84
|
try {
|
|
78
85
|
const encoded = client.format === "toml"
|
|
79
86
|
? stringifyToml(document)
|
|
80
87
|
: `${JSON.stringify(document, null, 2)}\n`;
|
|
81
|
-
await
|
|
88
|
+
await fileSystem.writeText(client.configPath, encoded);
|
|
82
89
|
const readback = objectSchema.parse(client.format === "toml"
|
|
83
|
-
? parseToml(await
|
|
84
|
-
: JSON.parse(await
|
|
90
|
+
? parseToml(await fileSystem.readText(client.configPath))
|
|
91
|
+
: JSON.parse(await fileSystem.readText(client.configPath)));
|
|
85
92
|
if (PRODUCT_IDENTITY.mcpServerKey in optionalObject(readback[key]))
|
|
86
93
|
throw new Error("registration readback mismatch");
|
|
87
94
|
return item(client.name, "removed", `Removed registration from ${client.configPath}.`);
|
|
88
95
|
}
|
|
89
96
|
catch {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
97
|
+
try {
|
|
98
|
+
await fileSystem.writeText(client.configPath, original);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return item(client.name, "failed", "Configuration could not be updated or restored. Restore its `.rea.backup` manually, then rerun uninstall.");
|
|
102
|
+
}
|
|
103
|
+
return item(client.name, "failed", "Configuration could not be updated. The original was restored and its `.rea.backup` was retained. Repair the configuration or restore the backup, then rerun uninstall.");
|
|
94
104
|
}
|
|
95
105
|
};
|
|
96
106
|
/** Ownership rule for persistent MCP registrations created by REA setup. */
|
|
@@ -107,18 +117,18 @@ const isOwnedRegistration = (value) => {
|
|
|
107
117
|
JSON.stringify(args) ===
|
|
108
118
|
JSON.stringify(["-y", PRODUCT_IDENTITY.packageName, "mcp"])));
|
|
109
119
|
};
|
|
110
|
-
const removeManagedPath = async (path, name) => {
|
|
120
|
+
const removeManagedPath = async (path, name, fileSystem) => {
|
|
111
121
|
try {
|
|
112
|
-
const stats = await
|
|
122
|
+
const stats = await fileSystem.stat(path);
|
|
113
123
|
if (stats.isSymbolicLink())
|
|
114
|
-
return item(name, "retained",
|
|
115
|
-
await
|
|
124
|
+
return item(name, "retained", "REA did not remove this item because its managed path is a symbolic link. Verify the link target before removing it manually.");
|
|
125
|
+
await fileSystem.remove(path);
|
|
116
126
|
return item(name, "removed", `Removed ${path}.`);
|
|
117
127
|
}
|
|
118
128
|
catch (cause) {
|
|
119
129
|
return isMissing(cause)
|
|
120
130
|
? item(name, "skipped", `${path} does not exist.`)
|
|
121
|
-
: item(name, "failed",
|
|
131
|
+
: item(name, "failed", "This item could not be removed. Check file permissions, then rerun uninstall.");
|
|
122
132
|
}
|
|
123
133
|
};
|
|
124
134
|
const item = (name, status, detail) => ({ name, status, detail });
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { createEvidence } from "../domain/evidence.js";
|
|
2
|
+
/** Provider identity for analyst-authored residual-unknown mutations. */
|
|
3
|
+
export const UNKNOWN_REGISTRY_PROVIDER = {
|
|
4
|
+
id: "rea-unknown-registry",
|
|
5
|
+
name: "REA residual unknown registry",
|
|
6
|
+
version: "1",
|
|
7
|
+
};
|
|
8
|
+
/** Create immutable evidence for one approved residual-unknown mutation. */
|
|
9
|
+
export const unknownMutationEvidence = (target, input) => createEvidence(target, UNKNOWN_REGISTRY_PROVIDER, {
|
|
10
|
+
predicateType: "rea.residual-unknown-mutation/v1",
|
|
11
|
+
operation: "record_unknown",
|
|
12
|
+
parameters: { domain: input.domain, severity: input.severity },
|
|
13
|
+
result: {
|
|
14
|
+
action: "record",
|
|
15
|
+
question: input.question,
|
|
16
|
+
required_authority: input.required_authority,
|
|
17
|
+
required_confidence: input.required_confidence,
|
|
18
|
+
},
|
|
19
|
+
confidence: "derived",
|
|
20
|
+
authority: "analyst-inference",
|
|
21
|
+
evidenceLinks: unknownEvidenceLinks(input),
|
|
22
|
+
limitations: [
|
|
23
|
+
"Registry mutation evidence records analyst intent, not proof of the answer.",
|
|
24
|
+
],
|
|
25
|
+
});
|
|
26
|
+
/** Deduplicate evidence links carried by an unknown mutation. */
|
|
27
|
+
export const unknownEvidenceLinks = (input) => [
|
|
28
|
+
...input.supporting_evidence_ids,
|
|
29
|
+
...input.contradicting_evidence_ids,
|
|
30
|
+
...("resolution" in input && input.resolution !== null
|
|
31
|
+
? input.resolution.evidence_ids
|
|
32
|
+
: []),
|
|
33
|
+
].filter((id, index, values) => values.indexOf(id) === index);
|
|
@@ -33,7 +33,7 @@ export const runUpgrade = async (currentVersion, host = systemUpgradeHost(), out
|
|
|
33
33
|
currentVersion,
|
|
34
34
|
latestVersion: latestVersion ?? null,
|
|
35
35
|
reason: "install",
|
|
36
|
-
remediation:
|
|
36
|
+
remediation: `REA could not update through npm. Check npm registry access and global install permissions, then run: ${UPGRADE_COMMAND}`,
|
|
37
37
|
};
|
|
38
38
|
return {
|
|
39
39
|
status: "upgraded",
|
|
@@ -42,6 +42,8 @@ export const runUpgrade = async (currentVersion, host = systemUpgradeHost(), out
|
|
|
42
42
|
versionCheck: latestVersion === undefined ? "unavailable" : "available",
|
|
43
43
|
installMethod: "npm",
|
|
44
44
|
command: UPGRADE_COMMAND,
|
|
45
|
+
clientRestartRequired: true,
|
|
46
|
+
remediation: "Rerun rea setup to refresh registrations and the skill, then restart clients that may retain an older MCP server.",
|
|
45
47
|
};
|
|
46
48
|
};
|
|
47
49
|
/** Create the npm registry and subprocess effects for a production upgrade. */
|
|
@@ -11,8 +11,8 @@ import { silentLogger } from "../logger.js";
|
|
|
11
11
|
*/
|
|
12
12
|
export const createBinarySession = (config, logger = silentLogger) => {
|
|
13
13
|
return new BinarySession(new CompositeProvider([
|
|
14
|
-
new ArtifactProvider(config.artifactNativeMountEnabled),
|
|
14
|
+
new ArtifactProvider(config.artifactNativeMountEnabled, config.artifactIntegrityContinueEnabled),
|
|
15
15
|
new NativeMacOSProvider(),
|
|
16
16
|
new HopperProvider(config, logger),
|
|
17
|
-
]));
|
|
17
|
+
]), { snapshotsEnabled: config.hopperLoaderArgs.length === 0 });
|
|
18
18
|
};
|
|
@@ -5,16 +5,15 @@ import { ARTIFACT_TOOL_CONTRACTS, artifactInventoryInputSchema, artifactExtracti
|
|
|
5
5
|
import { AnalysisCapabilityUnavailableError, ArtifactOperationError, } from "../domain/errors.js";
|
|
6
6
|
import { err, ok } from "../domain/result.js";
|
|
7
7
|
import { ArtifactReaderFailure, } from "./ArtifactReader.js";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
name: "REA safe artifact graph provider",
|
|
11
|
-
version: "1",
|
|
12
|
-
});
|
|
8
|
+
import { ARTIFACT_GRAPH_PROVIDER } from "../application/InvestigationProviders.js";
|
|
9
|
+
const IDENTITY = Object.freeze(ARTIFACT_GRAPH_PROVIDER);
|
|
13
10
|
/** Read-only inventory and exclusively owned extraction provider. */
|
|
14
11
|
export class ArtifactProvider {
|
|
15
12
|
nativeMountEnabled;
|
|
16
|
-
|
|
13
|
+
integrityContinueEnabled;
|
|
14
|
+
constructor(nativeMountEnabled = false, integrityContinueEnabled = false) {
|
|
17
15
|
this.nativeMountEnabled = nativeMountEnabled;
|
|
16
|
+
this.integrityContinueEnabled = integrityContinueEnabled;
|
|
18
17
|
}
|
|
19
18
|
#capabilities = Object.freeze(ARTIFACT_TOOL_CONTRACTS.map((contract) => Object.freeze({
|
|
20
19
|
provider: IDENTITY,
|
|
@@ -51,15 +50,17 @@ export class ArtifactProvider {
|
|
|
51
50
|
return this.#capabilities;
|
|
52
51
|
}
|
|
53
52
|
createClient(target) {
|
|
54
|
-
return new ArtifactClient(target, this.nativeMountEnabled);
|
|
53
|
+
return new ArtifactClient(target, this.nativeMountEnabled, this.integrityContinueEnabled);
|
|
55
54
|
}
|
|
56
55
|
}
|
|
57
56
|
class ArtifactClient {
|
|
58
57
|
target;
|
|
59
58
|
nativeMountEnabled;
|
|
60
|
-
|
|
59
|
+
integrityContinueEnabled;
|
|
60
|
+
constructor(target, nativeMountEnabled, integrityContinueEnabled) {
|
|
61
61
|
this.target = target;
|
|
62
62
|
this.nativeMountEnabled = nativeMountEnabled;
|
|
63
|
+
this.integrityContinueEnabled = integrityContinueEnabled;
|
|
63
64
|
}
|
|
64
65
|
async execute(operation, parameters, options) {
|
|
65
66
|
if (operation === "health")
|
|
@@ -102,6 +103,12 @@ class ArtifactClient {
|
|
|
102
103
|
nativeMountApproved: parsed.native_mount_approved === true,
|
|
103
104
|
nativeMountEnabled: this.nativeMountEnabled,
|
|
104
105
|
},
|
|
106
|
+
integrity: {
|
|
107
|
+
mode: parsed.integrity_policy,
|
|
108
|
+
approved: parsed.integrity_continue_approved === true,
|
|
109
|
+
enabled: this.integrityContinueEnabled,
|
|
110
|
+
maxMismatches: parsed.max_integrity_mismatches,
|
|
111
|
+
},
|
|
105
112
|
});
|
|
106
113
|
return ok(createAnalysisExecution(result, IDENTITY, {
|
|
107
114
|
rawResult: null,
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import canonicalize from "canonicalize";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { PROMPT_CONTRACTS } from "./contracts/promptContracts.js";
|
|
5
|
+
import { TOOL_CONTRACTS } from "./contracts/toolContracts.js";
|
|
6
|
+
/** Explicit CLI command inventory, kept separate from MCP tool counts. */
|
|
7
|
+
export const CLI_COMMAND_NAMES = [
|
|
8
|
+
"analyze",
|
|
9
|
+
"inspect",
|
|
10
|
+
"decompile",
|
|
11
|
+
"setup",
|
|
12
|
+
"doctor",
|
|
13
|
+
"uninstall",
|
|
14
|
+
"upgrade",
|
|
15
|
+
"xrefs",
|
|
16
|
+
"trace",
|
|
17
|
+
"capabilities",
|
|
18
|
+
"providers",
|
|
19
|
+
"function",
|
|
20
|
+
"search",
|
|
21
|
+
"import-reference-source",
|
|
22
|
+
"inventory-artifact",
|
|
23
|
+
"extract-artifact",
|
|
24
|
+
"inspect-macho",
|
|
25
|
+
"inspect-signature",
|
|
26
|
+
"list-architectures",
|
|
27
|
+
"inspect-plist",
|
|
28
|
+
"demangle-swift",
|
|
29
|
+
"evidence-import",
|
|
30
|
+
"evidence-export",
|
|
31
|
+
"compare",
|
|
32
|
+
"investigate-versions",
|
|
33
|
+
"capture-process",
|
|
34
|
+
"compare-process-captures",
|
|
35
|
+
"policy",
|
|
36
|
+
];
|
|
37
|
+
const toolCatalog = TOOL_CONTRACTS.map((contract) => ({
|
|
38
|
+
name: contract.name,
|
|
39
|
+
surface: contract.kind,
|
|
40
|
+
description: contract.description,
|
|
41
|
+
annotations: contract.annotations,
|
|
42
|
+
input_schema: z.toJSONSchema(contract.inputSchema, {
|
|
43
|
+
unrepresentable: "any",
|
|
44
|
+
}),
|
|
45
|
+
output_schema: z.toJSONSchema(contract.outputSchema, {
|
|
46
|
+
unrepresentable: "any",
|
|
47
|
+
}),
|
|
48
|
+
})).sort((left, right) => left.name.localeCompare(right.name));
|
|
49
|
+
const promptCatalog = PROMPT_CONTRACTS.map((contract) => ({
|
|
50
|
+
name: contract.name,
|
|
51
|
+
title: contract.title,
|
|
52
|
+
description: contract.description,
|
|
53
|
+
arguments: contract.arguments,
|
|
54
|
+
steps: contract.steps,
|
|
55
|
+
})).sort((left, right) => left.name.localeCompare(right.name));
|
|
56
|
+
const resourceCatalog = [
|
|
57
|
+
{ name: "server-identity", uri: "rea://server/identity" },
|
|
58
|
+
{ name: "active-residual-unknowns", uri: "rea://unknowns/active" },
|
|
59
|
+
];
|
|
60
|
+
const resourceTemplateCatalog = [
|
|
61
|
+
{ name: "session-evidence", uri_template: "rea://evidence/{evidenceId}" },
|
|
62
|
+
{
|
|
63
|
+
name: "evidence-section",
|
|
64
|
+
uri_template: "rea://evidence/{evidenceId}/section/{section}",
|
|
65
|
+
},
|
|
66
|
+
{ name: "residual-unknown", uri_template: "rea://unknown/{unknownId}" },
|
|
67
|
+
{
|
|
68
|
+
name: "analysis-snapshot",
|
|
69
|
+
uri_template: "rea://snapshot/{snapshotDigest}",
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: "artifact-page",
|
|
73
|
+
uri_template: "rea://artifact/{manifestId}/{collection}",
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: "function-dossier",
|
|
77
|
+
uri_template: "rea://function/{targetSha256}/{address}",
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: "investigation-workspace-revision",
|
|
81
|
+
uri_template: "rea://workspace/{workspaceId}/revision/{revision}",
|
|
82
|
+
},
|
|
83
|
+
];
|
|
84
|
+
const digest = (value) => {
|
|
85
|
+
const encoded = canonicalize(value);
|
|
86
|
+
if (encoded === undefined)
|
|
87
|
+
throw new TypeError("Catalog is not canonical JSON");
|
|
88
|
+
return createHash("sha256").update(encoded).digest("hex");
|
|
89
|
+
};
|
|
90
|
+
/** Stable, schema-sensitive identity for every public REA surface. */
|
|
91
|
+
export const CATALOG_IDENTITY = {
|
|
92
|
+
counts: {
|
|
93
|
+
cli_commands: CLI_COMMAND_NAMES.length,
|
|
94
|
+
mcp_tools: toolCatalog.length,
|
|
95
|
+
mcp_prompts: promptCatalog.length,
|
|
96
|
+
mcp_resources: resourceCatalog.length,
|
|
97
|
+
mcp_resource_templates: resourceTemplateCatalog.length,
|
|
98
|
+
},
|
|
99
|
+
digests: {
|
|
100
|
+
tools_sha256: digest(toolCatalog),
|
|
101
|
+
prompts_sha256: digest(promptCatalog),
|
|
102
|
+
resources_sha256: digest({ resourceCatalog, resourceTemplateCatalog }),
|
|
103
|
+
combined_sha256: digest({
|
|
104
|
+
cli: CLI_COMMAND_NAMES,
|
|
105
|
+
tools: toolCatalog,
|
|
106
|
+
prompts: promptCatalog,
|
|
107
|
+
resources: resourceCatalog,
|
|
108
|
+
resource_templates: resourceTemplateCatalog,
|
|
109
|
+
}),
|
|
110
|
+
},
|
|
111
|
+
tools: toolCatalog.map(({ name, surface, annotations }) => ({
|
|
112
|
+
name,
|
|
113
|
+
surface,
|
|
114
|
+
annotations: {
|
|
115
|
+
read_only: annotations.readOnlyHint ?? false,
|
|
116
|
+
destructive: annotations.destructiveHint ?? false,
|
|
117
|
+
idempotent: annotations.idempotentHint ?? false,
|
|
118
|
+
open_world: annotations.openWorldHint ?? true,
|
|
119
|
+
},
|
|
120
|
+
})),
|
|
121
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -13,6 +13,11 @@ import { parseConfig } from "./config.js";
|
|
|
13
13
|
import { importReferenceSource } from "./application/ReferenceSourceImport.js";
|
|
14
14
|
import { registerEvidenceCommands } from "./cliEvidenceCommands.js";
|
|
15
15
|
import { registerProcessCommands } from "./cliProcessCommands.js";
|
|
16
|
+
import { registerInvestigationCommands } from "./cliInvestigationCommands.js";
|
|
17
|
+
import { AnalysisProtocolError, PermissionRequiredError, projectAnalysisError, } from "./domain/errors.js";
|
|
18
|
+
import { projectReferenceSourceImportError } from "./application/ReferenceSourceImportTypes.js";
|
|
19
|
+
import { loadConfiguredPermissionAuthority } from "./application/PermissionConfiguration.js";
|
|
20
|
+
import { registerPolicyCommands } from "./cliPolicyCommands.js";
|
|
16
21
|
/**
|
|
17
22
|
* Build the one-shot Incur CLI without starting Hopper at import time.
|
|
18
23
|
* Analysis commands acquire and close their own sessions; bare `mcp` and
|
|
@@ -23,8 +28,8 @@ export const createCli = () => {
|
|
|
23
28
|
? "silent"
|
|
24
29
|
: parseLogLevel(process.env.REA_LOG_LEVEL));
|
|
25
30
|
const cli = Cli.create(PRODUCT_IDENTITY.cliBinary, {
|
|
26
|
-
version:
|
|
27
|
-
description: "Reverse engineer anything from your terminal or
|
|
31
|
+
version: PRODUCT_IDENTITY.packageVersion,
|
|
32
|
+
description: "Reverse engineer anything from your terminal or agent.",
|
|
28
33
|
mcp: {
|
|
29
34
|
command: PRODUCT_IDENTITY.mcpCommand,
|
|
30
35
|
instructions: "Ask what software, artifact, protocol, or behavior the user wants to understand, then choose the available investigation capabilities that can produce evidence.",
|
|
@@ -48,15 +53,22 @@ export const createCli = () => {
|
|
|
48
53
|
registerCapabilityCommands(cli, logger);
|
|
49
54
|
registerNativeCommands(cli, logger);
|
|
50
55
|
registerArtifactCommands(cli, logger);
|
|
56
|
+
registerInvestigationCommands(cli, logger);
|
|
51
57
|
registerEvidenceCommands(cli, logger);
|
|
52
58
|
registerReferenceSourceCommand(cli, logger);
|
|
53
59
|
registerProcessCommands(cli, logger);
|
|
60
|
+
registerPolicyCommands(cli, logger);
|
|
54
61
|
return cli;
|
|
55
62
|
};
|
|
56
63
|
const registerCoreCommands = (cli, logger) => {
|
|
57
64
|
const overviewOptions = z.object({
|
|
58
65
|
detail: z.enum(["concise", "detailed"]).default("concise"),
|
|
59
66
|
limit: z.number().int().min(1).max(50).default(10),
|
|
67
|
+
snapshot: z
|
|
68
|
+
.string()
|
|
69
|
+
.min(1)
|
|
70
|
+
.optional()
|
|
71
|
+
.describe("Load and update a local analysis snapshot"),
|
|
60
72
|
});
|
|
61
73
|
cli.command("analyze", {
|
|
62
74
|
description: "Get an overview of an app",
|
|
@@ -64,7 +76,7 @@ const registerCoreCommands = (cli, logger) => {
|
|
|
64
76
|
path: z.string().describe("App, program, or Hopper database path"),
|
|
65
77
|
}),
|
|
66
78
|
options: overviewOptions,
|
|
67
|
-
run: ({ args, options }) => logCliCommand(logger, "analyze", () => runDirectAnalysis(args.path, "binary_overview", { detail: options.detail, limit: options.limit }, logger)),
|
|
79
|
+
run: ({ args, options }) => logCliCommand(logger, "analyze", () => runDirectAnalysis(args.path, "binary_overview", { detail: options.detail, limit: options.limit }, { logger, snapshotPath: options.snapshot })),
|
|
68
80
|
});
|
|
69
81
|
cli.command("inspect", {
|
|
70
82
|
description: "Inspect an app overview with evidence",
|
|
@@ -72,7 +84,7 @@ const registerCoreCommands = (cli, logger) => {
|
|
|
72
84
|
path: z.string().describe("App, program, or Hopper database path"),
|
|
73
85
|
}),
|
|
74
86
|
options: overviewOptions,
|
|
75
|
-
run: ({ args, options }) => logCliCommand(logger, "inspect", () => runDirectAnalysis(args.path, "binary_overview", { detail: options.detail, limit: options.limit }, logger)),
|
|
87
|
+
run: ({ args, options }) => logCliCommand(logger, "inspect", () => runDirectAnalysis(args.path, "binary_overview", { detail: options.detail, limit: options.limit }, { logger, snapshotPath: options.snapshot })),
|
|
76
88
|
});
|
|
77
89
|
cli.command("decompile", {
|
|
78
90
|
description: "Read one part of an app as code",
|
|
@@ -80,12 +92,13 @@ const registerCoreCommands = (cli, logger) => {
|
|
|
80
92
|
path: z.string().describe("App or program path"),
|
|
81
93
|
address: z.string().describe("Procedure address"),
|
|
82
94
|
}),
|
|
83
|
-
|
|
95
|
+
options: z.object({ snapshot: z.string().min(1).optional() }),
|
|
96
|
+
run: ({ args, options }) => logCliCommand(logger, "decompile", () => runDirectAnalysis(args.path, "procedure_pseudo_code", { procedure: args.address }, { logger, snapshotPath: options.snapshot })),
|
|
84
97
|
});
|
|
85
98
|
};
|
|
86
99
|
const registerSetupCommands = (cli, logger) => {
|
|
87
100
|
cli.command("setup", {
|
|
88
|
-
description: "Install requirements and configure
|
|
101
|
+
description: "Install requirements and configure agents",
|
|
89
102
|
options: z.object({
|
|
90
103
|
yes: z
|
|
91
104
|
.boolean()
|
|
@@ -125,7 +138,7 @@ const registerSetupCommands = (cli, logger) => {
|
|
|
125
138
|
});
|
|
126
139
|
cli.command("upgrade", {
|
|
127
140
|
description: "Upgrade a global npm installation to the latest REA release",
|
|
128
|
-
run: ({ formatExplicit }) => logCliCommand(logger, "upgrade", () => runUpgrade(
|
|
141
|
+
run: ({ formatExplicit }) => logCliCommand(logger, "upgrade", () => runUpgrade(PRODUCT_IDENTITY.packageVersion, systemUpgradeHost(), formatExplicit ? "structured" : "human")),
|
|
129
142
|
});
|
|
130
143
|
};
|
|
131
144
|
const confirmSetup = async (actions) => {
|
|
@@ -153,7 +166,8 @@ const registerXrefsCommand = (cli, logger) => {
|
|
|
153
166
|
path: z.string().describe("App or program path"),
|
|
154
167
|
address: z.string().describe("Hexadecimal address"),
|
|
155
168
|
}),
|
|
156
|
-
|
|
169
|
+
options: z.object({ snapshot: z.string().min(1).optional() }),
|
|
170
|
+
run: ({ args, options }) => logCliCommand(logger, "xrefs", () => runDirectAnalysis(args.path, "xrefs", { address: args.address }, { logger, snapshotPath: options.snapshot })),
|
|
157
171
|
});
|
|
158
172
|
};
|
|
159
173
|
const registerTraceCommand = (cli, logger) => {
|
|
@@ -167,6 +181,7 @@ const registerTraceCommand = (cli, logger) => {
|
|
|
167
181
|
caseSensitive: z.boolean().default(false),
|
|
168
182
|
limit: z.number().int().min(1).max(100).default(20),
|
|
169
183
|
maxOperations: z.number().int().min(1).max(100).default(20),
|
|
184
|
+
snapshot: z.string().min(1).optional(),
|
|
170
185
|
}),
|
|
171
186
|
alias: {
|
|
172
187
|
caseSensitive: "case-sensitive",
|
|
@@ -177,7 +192,7 @@ const registerTraceCommand = (cli, logger) => {
|
|
|
177
192
|
case_sensitive: options.caseSensitive,
|
|
178
193
|
limit: options.limit,
|
|
179
194
|
max_operations: options.maxOperations,
|
|
180
|
-
}, logger)),
|
|
195
|
+
}, { logger, snapshotPath: options.snapshot })),
|
|
181
196
|
});
|
|
182
197
|
};
|
|
183
198
|
const registerCapabilityCommands = (cli, logger) => {
|
|
@@ -202,6 +217,7 @@ const registerFunctionCommand = (cli, logger) => {
|
|
|
202
217
|
limit: z.number().int().min(1).max(500).default(100),
|
|
203
218
|
maxPseudocodeChars: z.number().int().min(1).max(100_000).default(20_000),
|
|
204
219
|
maxInstructions: z.number().int().min(1).max(5_000).default(500),
|
|
220
|
+
snapshot: z.string().min(1).optional(),
|
|
205
221
|
}),
|
|
206
222
|
alias: {
|
|
207
223
|
includeAssembly: "include-assembly",
|
|
@@ -214,7 +230,7 @@ const registerFunctionCommand = (cli, logger) => {
|
|
|
214
230
|
limit: options.limit,
|
|
215
231
|
max_pseudocode_chars: options.maxPseudocodeChars,
|
|
216
232
|
max_instructions: options.maxInstructions,
|
|
217
|
-
}, logger)),
|
|
233
|
+
}, { logger, snapshotPath: options.snapshot })),
|
|
218
234
|
});
|
|
219
235
|
};
|
|
220
236
|
const registerSearchCommand = (cli, logger) => {
|
|
@@ -230,6 +246,7 @@ const registerSearchCommand = (cli, logger) => {
|
|
|
230
246
|
caseSensitive: z.boolean().default(false),
|
|
231
247
|
offset: z.number().int().min(0).default(0),
|
|
232
248
|
limit: z.number().int().min(1).max(100).default(100),
|
|
249
|
+
snapshot: z.string().min(1).optional(),
|
|
233
250
|
}),
|
|
234
251
|
alias: { caseSensitive: "case-sensitive" },
|
|
235
252
|
run: ({ args, options }) => logCliCommand(logger, "search", () => runDirectAnalysis(args.path, options.kind === "strings" ? "search_strings" : "search_procedures", {
|
|
@@ -238,7 +255,7 @@ const registerSearchCommand = (cli, logger) => {
|
|
|
238
255
|
case_sensitive: options.caseSensitive,
|
|
239
256
|
offset: options.offset,
|
|
240
257
|
limit: options.limit,
|
|
241
|
-
}, logger)),
|
|
258
|
+
}, { logger, snapshotPath: options.snapshot })),
|
|
242
259
|
});
|
|
243
260
|
};
|
|
244
261
|
const registerReferenceSourceCommand = (cli, logger) => {
|
|
@@ -252,7 +269,36 @@ const registerReferenceSourceCommand = (cli, logger) => {
|
|
|
252
269
|
run: ({ args }) => logCliCommand(logger, "import-reference-source", async () => {
|
|
253
270
|
const config = parseConfig(process.env);
|
|
254
271
|
if (!config.ok)
|
|
255
|
-
return {
|
|
272
|
+
return {
|
|
273
|
+
error: "Import failed",
|
|
274
|
+
...projectAnalysisError(config.error),
|
|
275
|
+
};
|
|
276
|
+
const authority = await loadConfiguredPermissionAuthority(config.value);
|
|
277
|
+
if (!authority.ok)
|
|
278
|
+
return {
|
|
279
|
+
error: "Import failed",
|
|
280
|
+
...projectAnalysisError(authority.error),
|
|
281
|
+
};
|
|
282
|
+
const authorized = await authority.value.authorize({
|
|
283
|
+
capability: "reference_read",
|
|
284
|
+
roots: [args.root],
|
|
285
|
+
executables: [],
|
|
286
|
+
environment_names: [],
|
|
287
|
+
network: "none",
|
|
288
|
+
mount: false,
|
|
289
|
+
operation_identity: `import_reference_source:${args.root}`,
|
|
290
|
+
}, "read");
|
|
291
|
+
if (!authorized.ok) {
|
|
292
|
+
const error = authorized.error instanceof PermissionRequiredError
|
|
293
|
+
? authorized.error
|
|
294
|
+
: new AnalysisProtocolError(authorized.error.message, {
|
|
295
|
+
cause: authorized.error,
|
|
296
|
+
});
|
|
297
|
+
return {
|
|
298
|
+
error: "Import failed",
|
|
299
|
+
...projectAnalysisError(error),
|
|
300
|
+
};
|
|
301
|
+
}
|
|
256
302
|
const imported = await importReferenceSource({
|
|
257
303
|
root: args.root,
|
|
258
304
|
caller: "rea-cli",
|
|
@@ -262,7 +308,10 @@ const registerReferenceSourceCommand = (cli, logger) => {
|
|
|
262
308
|
});
|
|
263
309
|
return imported.ok
|
|
264
310
|
? imported.value
|
|
265
|
-
: {
|
|
311
|
+
: {
|
|
312
|
+
error: "Import failed",
|
|
313
|
+
...projectReferenceSourceImportError(imported.error),
|
|
314
|
+
};
|
|
266
315
|
}),
|
|
267
316
|
});
|
|
268
317
|
};
|
|
@@ -275,7 +324,17 @@ const registerArtifactCommands = (cli, logger) => {
|
|
|
275
324
|
options: z.object({
|
|
276
325
|
offset: z.number().int().min(0).default(0),
|
|
277
326
|
limit: z.number().int().min(1).max(500).default(100),
|
|
327
|
+
integrityPolicy: z.enum(["fail", "record-and-continue"]).default("fail"),
|
|
328
|
+
integrityContinueApproved: z.boolean().default(false),
|
|
329
|
+
maxIntegrityMismatches: z.number().int().min(1).max(100).default(10),
|
|
330
|
+
nativeMountApproved: z.boolean().default(false),
|
|
278
331
|
}),
|
|
332
|
+
alias: {
|
|
333
|
+
integrityPolicy: "integrity-policy",
|
|
334
|
+
integrityContinueApproved: "integrity-continue-approved",
|
|
335
|
+
maxIntegrityMismatches: "max-integrity-mismatches",
|
|
336
|
+
nativeMountApproved: "native-mount-approved",
|
|
337
|
+
},
|
|
279
338
|
run: ({ args, options }) => logCliCommand(logger, "inventory-artifact", () => runProviderAnalysis(args.path, "inventory_artifact", {
|
|
280
339
|
node_offset: options.offset,
|
|
281
340
|
node_limit: options.limit,
|
|
@@ -283,6 +342,10 @@ const registerArtifactCommands = (cli, logger) => {
|
|
|
283
342
|
occurrence_limit: options.limit,
|
|
284
343
|
edge_offset: options.offset,
|
|
285
344
|
edge_limit: options.limit,
|
|
345
|
+
integrity_policy: options.integrityPolicy,
|
|
346
|
+
integrity_continue_approved: options.integrityContinueApproved,
|
|
347
|
+
max_integrity_mismatches: options.maxIntegrityMismatches,
|
|
348
|
+
native_mount_approved: options.nativeMountApproved,
|
|
286
349
|
}, logger)),
|
|
287
350
|
});
|
|
288
351
|
cli.command("extract-artifact", {
|