docs(cli): describe every command input

This commit is contained in:
morluto 2026-07-17 02:50:31 +00:00
parent ad17fe7aa7
commit d4be3e7f9a
11 changed files with 831 additions and 312 deletions

View File

@ -24,7 +24,7 @@
<li><span><span class="tsd-kind-parameter">value</span>: <span class="tsd-signature-type">Readonly</span><span class="tsd-signature-symbol">&lt;</span><span class="tsd-signature-type">Record</span><span class="tsd-signature-symbol">&lt;</span><span class="tsd-signature-type">string</span><span class="tsd-signature-symbol">,</span> <span class="tsd-signature-type">unknown</span><span class="tsd-signature-symbol">&gt;</span><span class="tsd-signature-symbol">&gt;</span></span></li></ul></div>
<h4 class="tsd-returns-title">Returns <span class="tsd-signature-type">Record</span><span class="tsd-signature-symbol">&lt;</span><span class="tsd-signature-type">string</span><span class="tsd-signature-symbol">,</span> <span class="tsd-signature-type">unknown</span><span class="tsd-signature-symbol">&gt;</span></h4><aside class="tsd-sources">
<ul>
<li>Defined in <a href="https://github.com/morluto/rea/blob/main/src/server/toolRegistrationOptions.ts#L62">src/server/toolRegistrationOptions.ts:62</a></li></ul></aside></div></li></ul></section></div>
<li>Defined in <a href="https://github.com/morluto/rea/blob/main/src/server/toolRegistrationOptions.ts#L67">src/server/toolRegistrationOptions.ts:67</a></li></ul></aside></div></li></ul></section></div>
<div class="col-sidebar">
<div class="page-menu">
<div class="tsd-navigation settings">

View File

@ -57,6 +57,7 @@ export function cliCommandOptionNames(
cli: unknown,
name: string,
): readonly string[];
export function cliCommandDescriptionIssues(cli: unknown): readonly string[];
export function serializeProductCatalog(
catalog: ProductCatalog,
): Promise<string>;

View File

@ -79,6 +79,31 @@ export const cliCommandOptionNames = (cli, name) => {
);
};
/** Report primary CLI fields whose generated JSON Schema lacks help text. */
export const cliCommandDescriptionIssues = (cli) => {
const commands = Cli.toCommands.get(cli);
if (commands === undefined)
throw new Error("Incur did not expose the registered REA CLI commands");
const missing = [];
for (const [commandName, command] of commands) {
if ("_alias" in command) continue;
for (const groupName of ["args", "options"]) {
if (!(groupName in command) || command[groupName] === undefined) continue;
const properties = z.toJSONSchema(command[groupName]).properties ?? {};
for (const [propertyName, property] of Object.entries(properties)) {
if (
typeof property !== "object" ||
property === null ||
typeof property.description !== "string" ||
property.description.trim() === ""
)
missing.push(`${commandName}.${groupName}.${propertyName}`);
}
}
}
return missing;
};
const assertSameNames = (label, actual, expected) => {
const actualSet = new Set(actual);
const expectedSet = new Set(expected);

View File

@ -106,8 +106,17 @@ const registerCoreCommands = (
logger: Logger,
): void => {
const overviewOptions = z.object({
detail: z.enum(["concise", "detailed"]).default("concise"),
limit: z.number().int().min(1).max(50).default(10),
detail: z
.enum(["concise", "detailed"])
.default("concise")
.describe("Overview detail level"),
limit: z
.number()
.int()
.min(1)
.max(50)
.default(10)
.describe("Maximum overview items to return"),
snapshot: z
.string()
.min(1)
@ -154,7 +163,11 @@ const registerCoreCommands = (
address: z.string().describe("Procedure address"),
}),
options: z.object({
snapshot: z.string().min(1).optional(),
snapshot: z
.string()
.min(1)
.optional()
.describe("Load and update a local analysis snapshot"),
provider: providerSelectionOption,
}),
run: ({ args, options }) =>
@ -269,7 +282,11 @@ const registerXrefsCommand = (
address: z.string().describe("Hexadecimal address"),
}),
options: z.object({
snapshot: z.string().min(1).optional(),
snapshot: z
.string()
.min(1)
.optional()
.describe("Load and update a local analysis snapshot"),
provider: providerSelectionOption,
}),
run: ({ args, options }) =>
@ -295,10 +312,29 @@ const registerTraceCommand = (
query: z.string().min(1).describe("Literal feature query"),
}),
options: z.object({
caseSensitive: z.boolean().default(false),
limit: z.number().int().min(1).max(100).default(20),
maxOperations: z.number().int().min(1).max(100).default(20),
snapshot: z.string().min(1).optional(),
caseSensitive: z
.boolean()
.default(false)
.describe("Match the query with exact letter case"),
limit: z
.number()
.int()
.min(1)
.max(100)
.default(20)
.describe("Maximum matching roots to examine"),
maxOperations: z
.number()
.int()
.min(1)
.max(100)
.default(20)
.describe("Maximum analysis operations in the trace"),
snapshot: z
.string()
.min(1)
.optional()
.describe("Load and update a local analysis snapshot"),
provider: providerSelectionOption,
}),
alias: {
@ -351,11 +387,36 @@ const registerFunctionCommand = (
address: z.string().describe("Procedure name or address"),
}),
options: z.object({
includeAssembly: z.boolean().default(false),
limit: z.number().int().min(1).max(500).default(100),
maxPseudocodeChars: z.number().int().min(1).max(100_000).default(20_000),
maxInstructions: z.number().int().min(1).max(5_000).default(500),
snapshot: z.string().min(1).optional(),
includeAssembly: z
.boolean()
.default(false)
.describe("Include bounded assembly instructions"),
limit: z
.number()
.int()
.min(1)
.max(500)
.default(100)
.describe("Maximum referenced items to return"),
maxPseudocodeChars: z
.number()
.int()
.min(1)
.max(100_000)
.default(20_000)
.describe("Maximum pseudocode characters to return"),
maxInstructions: z
.number()
.int()
.min(1)
.max(5_000)
.default(500)
.describe("Maximum assembly instructions to return"),
snapshot: z
.string()
.min(1)
.optional()
.describe("Load and update a local analysis snapshot"),
provider: providerSelectionOption,
}),
alias: {
@ -392,12 +453,36 @@ const registerSearchCommand = (
pattern: z.string().min(1).describe("Literal text or regex pattern"),
}),
options: z.object({
kind: z.enum(["strings", "procedures"]).default("strings"),
mode: z.enum(["literal", "regex"]).default("literal"),
caseSensitive: z.boolean().default(false),
offset: z.number().int().min(0).default(0),
limit: z.number().int().min(1).max(100).default(100),
snapshot: z.string().min(1).optional(),
kind: z
.enum(["strings", "procedures"])
.default("strings")
.describe("Analyzed item kind to search"),
mode: z
.enum(["literal", "regex"])
.default("literal")
.describe("Interpret the pattern as literal text or a regex"),
caseSensitive: z
.boolean()
.default(false)
.describe("Match the pattern with exact letter case"),
offset: z
.number()
.int()
.min(0)
.default(0)
.describe("Zero-based matching-item offset"),
limit: z
.number()
.int()
.min(1)
.max(100)
.default(100)
.describe("Maximum matching items to return"),
snapshot: z
.string()
.min(1)
.optional()
.describe("Load and update a local analysis snapshot"),
provider: providerSelectionOption,
}),
alias: { caseSensitive: "case-sensitive" },
@ -495,12 +580,38 @@ const registerArtifactCommands = (
path: z.string().describe("Application or package path"),
}),
options: z.object({
offset: z.number().int().min(0).default(0),
limit: z.number().int().min(1).max(500).default(100),
integrityPolicy: z.enum(["fail", "record-and-continue"]).default("fail"),
integrityContinueApproved: z.boolean().default(false),
maxIntegrityMismatches: z.number().int().min(1).max(100).default(10),
nativeMountApproved: z.boolean().default(false),
offset: z
.number()
.int()
.min(0)
.default(0)
.describe("Zero-based offset for graph nodes, occurrences, and edges"),
limit: z
.number()
.int()
.min(1)
.max(500)
.default(100)
.describe("Maximum nodes, occurrences, and edges per result section"),
integrityPolicy: z
.enum(["fail", "record-and-continue"])
.default("fail")
.describe("Behavior when declared artifact integrity does not match"),
integrityContinueApproved: z
.boolean()
.default(false)
.describe("Approve continuing after recorded integrity mismatches"),
maxIntegrityMismatches: z
.number()
.int()
.min(1)
.max(100)
.default(10)
.describe("Maximum integrity mismatches to record before stopping"),
nativeMountApproved: z
.boolean()
.default(false)
.describe("Approve read-only native mounting when required"),
}),
alias: {
integrityPolicy: "integrity-policy",
@ -537,7 +648,8 @@ const registerArtifactCommands = (
occurrenceIds: z
.array(z.string().regex(/^occ_[a-f0-9]{64}$/u))
.min(1)
.max(500),
.max(500)
.describe("Exact artifact occurrence IDs selected for extraction"),
}),
alias: { outputRoot: "output-root", occurrenceIds: "occurrence-ids" },
run: ({ args }) =>
@ -556,6 +668,49 @@ const registerArtifactCommands = (
});
};
const managedOffset = (subject: string) =>
z.number().int().min(0).default(0).describe(`Zero-based ${subject} offset`);
const managedLimit = (subject: string, maximum: number, fallback: number) =>
z
.number()
.int()
.min(1)
.max(maximum)
.default(fallback)
.describe(`Maximum ${subject} to return`);
const managedInspectionLimits = {
maxFileBytes: z
.number()
.int()
.min(4_096)
.max(1_073_741_824)
.default(268_435_456)
.describe("Maximum managed artifact file size accepted for inspection"),
maxMetadataBytes: z
.number()
.int()
.min(256)
.max(268_435_456)
.default(67_108_864)
.describe("Maximum CLI metadata region size accepted for inspection"),
maxTableRows: z
.number()
.int()
.min(1)
.max(1_000_000)
.default(100_000)
.describe("Maximum rows accepted in any CLI metadata table"),
maxHeapItemBytes: z
.number()
.int()
.min(1)
.max(16_777_216)
.default(1_048_576)
.describe("Maximum bytes accepted for one CLI metadata heap item"),
};
const registerManagedCommands = (
cli: ReturnType<typeof Cli.create>,
logger: Logger,
@ -566,31 +721,46 @@ const registerManagedCommands = (
path: z.string().describe("Managed PE executable or assembly path"),
}),
options: z.object({
referenceOffset: z.number().int().min(0).default(0),
referenceLimit: z.number().int().min(1).max(500).default(100),
resourceOffset: z.number().int().min(0).default(0),
resourceLimit: z.number().int().min(1).max(500).default(100),
attributeOffset: z.number().int().min(0).default(0),
attributeLimit: z.number().int().min(1).max(500).default(100),
maxFileBytes: z
referenceOffset: z
.number()
.int()
.min(4_096)
.max(1_073_741_824)
.default(268_435_456),
maxMetadataBytes: z
.number()
.int()
.min(256)
.max(268_435_456)
.default(67_108_864),
maxTableRows: z.number().int().min(1).max(1_000_000).default(100_000),
maxHeapItemBytes: z
.min(0)
.default(0)
.describe("Zero-based assembly-reference offset"),
referenceLimit: z
.number()
.int()
.min(1)
.max(16_777_216)
.default(1_048_576),
.max(500)
.default(100)
.describe("Maximum assembly references to return"),
resourceOffset: z
.number()
.int()
.min(0)
.default(0)
.describe("Zero-based manifest-resource offset"),
resourceLimit: z
.number()
.int()
.min(1)
.max(500)
.default(100)
.describe("Maximum manifest resources to return"),
attributeOffset: z
.number()
.int()
.min(0)
.default(0)
.describe("Zero-based assembly-attribute offset"),
attributeLimit: z
.number()
.int()
.min(1)
.max(500)
.default(100)
.describe("Maximum assembly attributes to return"),
...managedInspectionLimits,
}),
alias: {
referenceOffset: "reference-offset",
@ -632,48 +802,38 @@ const registerManagedCommands = (
path: z.string().describe("Managed PE executable or assembly path"),
}),
options: z.object({
typeOffset: z.number().int().min(0).default(0),
typeLimit: z.number().int().min(1).max(500).default(100),
methodOffset: z.number().int().min(0).default(0),
methodLimit: z.number().int().min(1).max(500).default(100),
fieldOffset: z.number().int().min(0).default(0),
fieldLimit: z.number().int().min(1).max(500).default(100),
memberRefOffset: z.number().int().min(0).default(0),
memberRefLimit: z.number().int().min(1).max(500).default(100),
edgeOffset: z.number().int().min(0).default(0),
edgeLimit: z.number().int().min(1).max(1_000).default(250),
instructionAnchorLimit: z.number().int().min(0).max(500).default(100),
maxFileBytes: z
typeOffset: managedOffset("type definition"),
typeLimit: managedLimit("type definitions", 500, 100),
methodOffset: managedOffset("method definition"),
methodLimit: managedLimit("method definitions", 500, 100),
fieldOffset: managedOffset("field definition"),
fieldLimit: managedLimit("field definitions", 500, 100),
memberRefOffset: managedOffset("member reference"),
memberRefLimit: managedLimit("member references", 500, 100),
edgeOffset: managedOffset("member edge"),
edgeLimit: managedLimit("member edges", 1_000, 250),
instructionAnchorLimit: z
.number()
.int()
.min(4_096)
.max(1_073_741_824)
.default(268_435_456),
maxMetadataBytes: z
.number()
.int()
.min(256)
.max(268_435_456)
.default(67_108_864),
maxTableRows: z.number().int().min(1).max(1_000_000).default(100_000),
maxHeapItemBytes: z
.number()
.int()
.min(1)
.max(16_777_216)
.default(1_048_576),
.min(0)
.max(500)
.default(100)
.describe("Maximum CIL instruction anchors per method"),
...managedInspectionLimits,
maxMethodBodyBytes: z
.number()
.int()
.min(1)
.max(16_777_216)
.default(1_048_576),
.default(1_048_576)
.describe("Maximum CIL method body size accepted for inspection"),
maxMethodInstructions: z
.number()
.int()
.min(1)
.max(100_000)
.default(10_000),
.default(10_000)
.describe("Maximum decoded CIL instructions per method"),
}),
alias: {
typeOffset: "type-offset",
@ -729,31 +889,13 @@ const registerManagedCommands = (
path: z.string().describe("Managed PE executable or assembly path"),
}),
options: z.object({
moduleRefOffset: z.number().int().min(0).default(0),
moduleRefLimit: z.number().int().min(1).max(500).default(100),
importOffset: z.number().int().min(0).default(0),
importLimit: z.number().int().min(1).max(500).default(100),
implementationOffset: z.number().int().min(0).default(0),
implementationLimit: z.number().int().min(1).max(500).default(100),
maxFileBytes: z
.number()
.int()
.min(4_096)
.max(1_073_741_824)
.default(268_435_456),
maxMetadataBytes: z
.number()
.int()
.min(256)
.max(268_435_456)
.default(67_108_864),
maxTableRows: z.number().int().min(1).max(1_000_000).default(100_000),
maxHeapItemBytes: z
.number()
.int()
.min(1)
.max(16_777_216)
.default(1_048_576),
moduleRefOffset: managedOffset("module reference"),
moduleRefLimit: managedLimit("module references", 500, 100),
importOffset: managedOffset("platform import"),
importLimit: managedLimit("platform imports", 500, 100),
implementationOffset: managedOffset("native implementation"),
implementationLimit: managedLimit("native implementations", 500, 100),
...managedInspectionLimits,
}),
alias: {
moduleRefOffset: "module-ref-offset",
@ -800,46 +942,54 @@ const registerManagedCommands = (
.describe("Candidate managed PE executable or assembly"),
}),
options: z.object({
maxMethodMatches: z.number().int().min(1).max(50_000).default(10_000),
maxFieldMatches: z.number().int().min(0).max(50_000).default(5_000),
maxCandidates: z.number().int().min(1).max(500).default(50),
typeLimit: z.number().int().min(1).max(500).default(500),
methodLimit: z.number().int().min(1).max(500).default(500),
fieldLimit: z.number().int().min(1).max(500).default(500),
memberRefLimit: z.number().int().min(1).max(500).default(500),
edgeLimit: z.number().int().min(1).max(1_000).default(1_000),
instructionAnchorLimit: z.number().int().min(0).max(500).default(500),
maxFileBytes: z
.number()
.int()
.min(4_096)
.max(1_073_741_824)
.default(268_435_456),
maxMetadataBytes: z
.number()
.int()
.min(256)
.max(268_435_456)
.default(67_108_864),
maxTableRows: z.number().int().min(1).max(1_000_000).default(100_000),
maxHeapItemBytes: z
maxMethodMatches: z
.number()
.int()
.min(1)
.max(16_777_216)
.default(1_048_576),
.max(50_000)
.default(10_000)
.describe("Maximum exact method matches to return"),
maxFieldMatches: z
.number()
.int()
.min(0)
.max(50_000)
.default(5_000)
.describe("Maximum exact field matches to return"),
maxCandidates: z
.number()
.int()
.min(1)
.max(500)
.default(50)
.describe("Maximum ambiguous candidates per unmatched member"),
typeLimit: managedLimit("type definitions per artifact", 500, 500),
methodLimit: managedLimit("method definitions per artifact", 500, 500),
fieldLimit: managedLimit("field definitions per artifact", 500, 500),
memberRefLimit: managedLimit("member references per artifact", 500, 500),
edgeLimit: managedLimit("member edges per artifact", 1_000, 1_000),
instructionAnchorLimit: z
.number()
.int()
.min(0)
.max(500)
.default(500)
.describe("Maximum CIL instruction anchors per compared method"),
...managedInspectionLimits,
maxMethodBodyBytes: z
.number()
.int()
.min(1)
.max(16_777_216)
.default(1_048_576),
.default(1_048_576)
.describe("Maximum CIL method body size accepted for comparison"),
maxMethodInstructions: z
.number()
.int()
.min(1)
.max(100_000)
.default(100_000),
.default(100_000)
.describe("Maximum decoded CIL instructions per compared method"),
}),
alias: {
maxMethodMatches: "max-method-matches",
@ -1006,7 +1156,10 @@ const registerNativeCommands = (
description: "Parse app plist metadata without launching Hopper",
args: z.object({ path: z.string().describe("App or Mach-O path") }),
options: z.object({
relativePath: z.string().default("Contents/Info.plist"),
relativePath: z
.string()
.default("Contents/Info.plist")
.describe("Plist path relative to the app root"),
}),
alias: { relativePath: "relative-path" },
run: ({ args, options }) =>
@ -1023,7 +1176,11 @@ const registerNativeCommands = (
description: "Demangle a bounded Swift symbol batch without Hopper",
args: z.object({
path: z.string().describe("Artifact path used for evidence identity"),
symbols: z.array(z.string().min(1)).min(1).max(500),
symbols: z
.array(z.string().min(1))
.min(1)
.max(500)
.describe("Swift mangled symbols to demangle"),
}),
run: ({ args }) =>
logCliCommand(logger, "demangle-swift", () =>

View File

@ -22,8 +22,13 @@ import type { Logger } from "./logger.js";
import { CLI_COMMANDS } from "./cliCommandNames.js";
const scopeOptions = {
allowedOrigins: z.array(z.string().min(1)).optional(),
approved: z.boolean().default(false),
allowedOrigins: z
.array(z.string().min(1))
.optional()
.describe(
"Exact origins to observe; defaults to REA_BROWSER_ALLOWED_ORIGINS_JSON",
),
approved: z.boolean().default(false).describe("Approve passive observation"),
};
/** Register WebMCP, capture-diff, and screenshot CLI equivalents. */
@ -43,19 +48,47 @@ const registerWebMcp = (
): void => {
cli.command(CLI_COMMANDS.discoverWebMcpTools, {
description: "Passively discover page-declared WebMCP tools",
args: z.object({ endpoint: z.string(), targetId: z.string() }),
args: z.object({
endpoint: z.string().describe("Configured loopback CDP HTTP endpoint"),
targetId: z.string().describe("Target ID from list-browser-targets"),
}),
options: z.object({
...scopeOptions,
observationMs: z.number().int().min(0).max(10_000).default(100),
maxTools: z.number().int().min(1).max(5_000).default(500),
observationMs: z
.number()
.int()
.min(0)
.max(10_000)
.default(100)
.describe("Observation duration in milliseconds"),
maxTools: z
.number()
.int()
.min(1)
.max(5_000)
.default(500)
.describe("Maximum page-declared WebMCP tools to return"),
maxSchemaBytes: z
.number()
.int()
.min(1)
.max(1_024 * 1_024)
.default(256 * 1_024),
maxSchemaNodes: z.number().int().min(1).max(100_000).default(5_000),
maxSchemaDepth: z.number().int().min(1).max(100).default(20),
.default(256 * 1_024)
.describe("Maximum serialized size of one declared tool schema"),
maxSchemaNodes: z
.number()
.int()
.min(1)
.max(100_000)
.default(5_000)
.describe("Maximum nodes in one declared tool schema"),
maxSchemaDepth: z
.number()
.int()
.min(1)
.max(100)
.default(20)
.describe("Maximum nesting depth in one declared tool schema"),
}),
run: ({ args, options }) =>
logCliCommand(logger, "discover-webmcp-tools", async () => {
@ -90,9 +123,18 @@ const registerCaptureDiff = (
): void => {
cli.command(CLI_COMMANDS.compareWebCaptures, {
description: "Compare two normalized web capture JSON values",
args: z.object({ beforeJson: z.string(), afterJson: z.string() }),
args: z.object({
beforeJson: z.string().describe("Earlier normalized web capture JSON"),
afterJson: z.string().describe("Later normalized web capture JSON"),
}),
options: z.object({
maxChanges: z.number().int().min(1).max(20_000).default(2_000),
maxChanges: z
.number()
.int()
.min(1)
.max(20_000)
.default(2_000)
.describe("Maximum normalized changes to return"),
}),
run: ({ args, options }) =>
logCliCommand(logger, "compare-web-captures", async () => {
@ -119,16 +161,23 @@ const registerScreenshot = (
): void => {
cli.command(CLI_COMMANDS.captureWebScreenshot, {
description: "Capture an explicitly approved visible page viewport",
args: z.object({ endpoint: z.string(), targetId: z.string() }),
args: z.object({
endpoint: z.string().describe("Configured loopback CDP HTTP endpoint"),
targetId: z.string().describe("Target ID from list-browser-targets"),
}),
options: z.object({
...scopeOptions,
screenshotApproved: z.boolean().default(false),
screenshotApproved: z
.boolean()
.default(false)
.describe("Approve capturing the visible page viewport"),
maximumImageBytes: z
.number()
.int()
.min(1)
.max(8 * 1_024 * 1_024)
.default(4 * 1_024 * 1_024),
.default(4 * 1_024 * 1_024)
.describe("Maximum captured PNG size"),
}),
run: ({ args, options }) =>
logCliCommand(logger, "capture-web-screenshot", async () => {
@ -160,15 +209,25 @@ const registerScreenshotDiff = (
): void => {
cli.command(CLI_COMMANDS.compareWebScreenshots, {
description: "Compare two self-verifying PNG artifact JSON values",
args: z.object({ beforeJson: z.string(), afterJson: z.string() }),
args: z.object({
beforeJson: z.string().describe("Earlier screenshot artifact JSON"),
afterJson: z.string().describe("Later screenshot artifact JSON"),
}),
options: z.object({
channelThreshold: z.number().int().min(0).max(255).default(0),
channelThreshold: z
.number()
.int()
.min(0)
.max(255)
.default(0)
.describe("Per-channel difference threshold for changed pixels"),
maximumPixels: z
.number()
.int()
.min(1)
.max(32_000_000)
.default(16_000_000),
.default(16_000_000)
.describe("Maximum decoded pixels per screenshot"),
}),
run: ({ args, options }) =>
logCliCommand(logger, "compare-web-screenshots", async () => {

View File

@ -31,6 +31,23 @@ const browserScopeOptions = {
approved: z.boolean().default(false).describe("Approve passive observation"),
};
const boundedCount = (
subject: string,
maximum: number,
fallback: number,
minimum = 1,
) =>
z
.number()
.int()
.min(minimum)
.max(maximum)
.default(fallback)
.describe(`Maximum ${subject}`);
const boundedBytes = (subject: string, maximum: number, fallback: number) =>
boundedCount(`${subject} in bytes`, maximum, fallback);
/** Register CLI equivalents of the passive browser MCP tools. */
export const registerBrowserCommands = (
cli: ReturnType<typeof Cli.create>,
@ -53,8 +70,13 @@ const registerTargetList = (
}),
options: z.object({
...browserScopeOptions,
offset: z.number().int().min(0).default(0),
limit: z.number().int().min(1).max(200).default(100),
offset: z
.number()
.int()
.min(0)
.default(0)
.describe("Zero-based browser-target offset"),
limit: boundedCount("browser targets to return", 200, 100),
}),
run: ({ args, options }) =>
logCliCommand(logger, "list-browser-targets", async () => {
@ -92,88 +114,114 @@ const registerPageInspection = (
}),
options: z.object({
...browserScopeOptions,
observationMs: z.number().int().min(0).max(10_000).default(500),
includeAccessibilityText: z.boolean().default(false),
includeConsoleText: z.boolean().default(false),
consoleTextApproved: z.boolean().default(false),
includeJsonBodyShapes: z.boolean().default(false),
jsonBodySchemaApproved: z.boolean().default(false),
includeWebsocketShapes: z.boolean().default(false),
websocketShapeApproved: z.boolean().default(false),
includeScriptSources: z.boolean().default(false),
includeStorageKeys: z.boolean().default(false),
maxFrames: z.number().int().min(1).max(1_000).default(200),
maxDomNodes: z.number().int().min(1).max(10_000).default(2_000),
maxAxNodes: z.number().int().min(1).max(10_000).default(2_000),
maxAxTextFieldBytes: z
.number()
.int()
.min(1)
.max(16 * 1_024)
.default(1_024),
maxTotalAxTextBytes: z
.number()
.int()
.min(1)
.max(1_024 * 1_024)
.default(64 * 1_024),
maxScripts: z.number().int().min(1).max(1_000).default(200),
maxResources: z.number().int().min(1).max(10_000).default(2_000),
maxWorkers: z.number().int().min(1).max(5_000).default(500),
maxStorageKeys: z.number().int().min(1).max(10_000).default(1_000),
maxScriptSourceBytes: z
.number()
.int()
.min(1)
.max(4 * 1_024 * 1_024)
.default(1_024 * 1_024),
maxTotalScriptSourceBytes: z
.number()
.int()
.min(1)
.max(16 * 1_024 * 1_024)
.default(4 * 1_024 * 1_024),
maxNetworkEvents: z.number().int().min(1).max(10_000).default(1_000),
maxConsoleEvents: z.number().int().min(1).max(2_000).default(200),
maxConsoleTextFieldBytes: z
.number()
.int()
.min(1)
.max(16 * 1_024)
.default(1_024),
maxTotalConsoleTextBytes: z
.number()
.int()
.min(1)
.max(1_024 * 1_024)
.default(64 * 1_024),
maxJsonBodyBytes: z
.number()
.int()
.min(1)
.max(4 * 1_024 * 1_024)
.default(1_024 * 1_024),
maxTotalJsonBodyBytes: z
.number()
.int()
.min(1)
.max(16 * 1_024 * 1_024)
.default(4 * 1_024 * 1_024),
maxJsonShapeNodes: z.number().int().min(1).max(100_000).default(5_000),
maxJsonShapeDepth: z.number().int().min(1).max(100).default(20),
maxWebsocketEvents: z.number().int().min(1).max(5_000).default(500),
maxWebsocketShapeBytes: z
.number()
.int()
.min(1)
.max(1_024 * 1_024)
.default(64 * 1_024),
maxTotalWebsocketShapeBytes: z
.number()
.int()
.min(1)
.max(16 * 1_024 * 1_024)
.default(1_024 * 1_024),
observationMs: boundedCount(
"observation duration in milliseconds",
10_000,
500,
0,
),
includeAccessibilityText: z
.boolean()
.default(false)
.describe("Include bounded accessibility text"),
includeConsoleText: z
.boolean()
.default(false)
.describe("Include bounded console message text"),
consoleTextApproved: z
.boolean()
.default(false)
.describe("Approve capturing console message text"),
includeJsonBodyShapes: z
.boolean()
.default(false)
.describe("Include structural shapes of JSON response bodies"),
jsonBodySchemaApproved: z
.boolean()
.default(false)
.describe(
"Approve inspecting JSON response bodies for structural shapes",
),
includeWebsocketShapes: z
.boolean()
.default(false)
.describe("Include structural shapes of WebSocket payloads"),
websocketShapeApproved: z
.boolean()
.default(false)
.describe(
"Approve inspecting WebSocket payloads for structural shapes",
),
includeScriptSources: z
.boolean()
.default(false)
.describe("Include bounded JavaScript source text"),
includeStorageKeys: z
.boolean()
.default(false)
.describe("Include storage key names without values"),
maxFrames: boundedCount("page frames", 1_000, 200),
maxDomNodes: boundedCount("DOM nodes", 10_000, 2_000),
maxAxNodes: boundedCount("accessibility nodes", 10_000, 2_000),
maxAxTextFieldBytes: boundedBytes(
"one accessibility text field",
16 * 1_024,
1_024,
),
maxTotalAxTextBytes: boundedBytes(
"total accessibility text",
1_024 * 1_024,
64 * 1_024,
),
maxScripts: boundedCount("scripts", 1_000, 200),
maxResources: boundedCount("resources", 10_000, 2_000),
maxWorkers: boundedCount("workers", 5_000, 500),
maxStorageKeys: boundedCount("storage keys", 10_000, 1_000),
maxScriptSourceBytes: boundedBytes(
"one script source",
4 * 1_024 * 1_024,
1_024 * 1_024,
),
maxTotalScriptSourceBytes: boundedBytes(
"total script source",
16 * 1_024 * 1_024,
4 * 1_024 * 1_024,
),
maxNetworkEvents: boundedCount("network events", 10_000, 1_000),
maxConsoleEvents: boundedCount("console events", 2_000, 200),
maxConsoleTextFieldBytes: boundedBytes(
"one console text field",
16 * 1_024,
1_024,
),
maxTotalConsoleTextBytes: boundedBytes(
"total console text",
1_024 * 1_024,
64 * 1_024,
),
maxJsonBodyBytes: boundedBytes(
"one JSON response body",
4 * 1_024 * 1_024,
1_024 * 1_024,
),
maxTotalJsonBodyBytes: boundedBytes(
"total JSON response bodies",
16 * 1_024 * 1_024,
4 * 1_024 * 1_024,
),
maxJsonShapeNodes: boundedCount("JSON shape nodes", 100_000, 5_000),
maxJsonShapeDepth: boundedCount("JSON shape depth", 100, 20),
maxWebsocketEvents: boundedCount("WebSocket events", 5_000, 500),
maxWebsocketShapeBytes: boundedBytes(
"one WebSocket shape",
1_024 * 1_024,
64 * 1_024,
),
maxTotalWebsocketShapeBytes: boundedBytes(
"total WebSocket shapes",
16 * 1_024 * 1_024,
1_024 * 1_024,
),
}),
run: ({ args, options }) =>
logCliCommand(logger, "inspect-web-page", async () => {
@ -245,32 +293,45 @@ const registerBundleAnalysis = (
}),
options: z.object({
...browserScopeOptions,
sourceCaptureApproved: z.boolean().default(false),
observationMs: z.number().int().min(0).max(10_000).default(500),
fetchSourceMaps: z.boolean().default(false),
sourceMapFetchApproved: z.boolean().default(false),
maxFindings: z.number().int().min(1).max(10_000).default(1_000),
maxAstNodes: z.number().int().min(1).max(2_000_000).default(250_000),
maxSourceMaps: z.number().int().min(1).max(1_000).default(100),
maxSourceMapBytes: z
.number()
.int()
.min(1)
.max(16 * 1_024 * 1_024)
.default(4 * 1_024 * 1_024),
maxTotalSourceMapBytes: z
.number()
.int()
.min(1)
.max(64 * 1_024 * 1_024)
.default(16 * 1_024 * 1_024),
maxSourceMapMappings: z
.number()
.int()
.min(1)
.max(100_000)
.default(10_000),
maxOriginalSources: z.number().int().min(1).max(20_000).default(2_000),
sourceCaptureApproved: z
.boolean()
.default(false)
.describe("Approve capturing bounded script source text"),
observationMs: boundedCount(
"observation duration in milliseconds",
10_000,
500,
0,
),
fetchSourceMaps: z
.boolean()
.default(false)
.describe("Fetch source maps referenced by captured scripts"),
sourceMapFetchApproved: z
.boolean()
.default(false)
.describe(
"Approve fetching referenced source maps from allowed origins",
),
maxFindings: boundedCount("static-analysis findings", 10_000, 1_000),
maxAstNodes: boundedCount("JavaScript AST nodes", 2_000_000, 250_000),
maxSourceMaps: boundedCount("source maps", 1_000, 100),
maxSourceMapBytes: boundedBytes(
"one source map",
16 * 1_024 * 1_024,
4 * 1_024 * 1_024,
),
maxTotalSourceMapBytes: boundedBytes(
"total source maps",
64 * 1_024 * 1_024,
16 * 1_024 * 1_024,
),
maxSourceMapMappings: boundedCount(
"decoded source-map mappings",
100_000,
10_000,
),
maxOriginalSources: boundedCount("original source files", 20_000, 2_000),
}),
run: ({ args, options }) =>
logCliCommand(logger, "analyze-web-bundle", async () => {
@ -321,8 +382,12 @@ const registerObservationSession = (
}),
options: z.object({
...browserScopeOptions,
observationMs: z.number().int().min(1).max(60_000).default(10_000),
maxTimelineEvents: z.number().int().min(1).max(20_000).default(2_000),
observationMs: boundedCount(
"observation duration in milliseconds",
60_000,
10_000,
),
maxTimelineEvents: boundedCount("timeline events", 20_000, 2_000),
}),
run: ({ args, options }) =>
logCliCommand(logger, "observe-web-session", async () => {

View File

@ -26,7 +26,7 @@ const scopeOptions = {
.array(z.string().min(1))
.optional()
.describe("Filesystem roots; defaults to REA_ELECTRON_FILE_ROOTS_JSON"),
approved: z.boolean().default(false),
approved: z.boolean().default(false).describe("Approve passive observation"),
};
/** Register CLI equivalents of the Electron MCP tools. */
@ -76,11 +76,26 @@ const registerElectronObservationCommands = (
): void => {
cli.command(CLI_COMMANDS.listElectronTargets, {
description: "List root-confined file pages from Electron CDP",
args: z.object({ endpoint: z.string() }),
args: z.object({
endpoint: z
.string()
.describe("Configured loopback Electron CDP endpoint"),
}),
options: z.object({
...scopeOptions,
offset: z.number().int().min(0).default(0),
limit: z.number().int().min(1).max(200).default(100),
offset: z
.number()
.int()
.min(0)
.default(0)
.describe("Zero-based Electron-target offset"),
limit: z
.number()
.int()
.min(1)
.max(200)
.default(100)
.describe("Maximum Electron targets to return"),
}),
run: ({ args, options }) =>
logCliCommand(logger, "list-electron-targets", async () => {
@ -105,29 +120,78 @@ const registerElectronObservationCommands = (
});
cli.command(CLI_COMMANDS.inspectElectronPage, {
description: "Passively inspect one root-confined Electron file page",
args: z.object({ endpoint: z.string(), targetId: z.string() }),
args: z.object({
endpoint: z
.string()
.describe("Configured loopback Electron CDP endpoint"),
targetId: z.string().describe("Target ID from list-electron-targets"),
}),
options: z.object({
...scopeOptions,
observationMs: z.number().int().min(0).max(10_000).default(100),
includeScriptSources: z.boolean().default(false),
sourceCaptureApproved: z.boolean().default(false),
maxFrames: z.number().int().min(1).max(1_000).default(200),
maxDomNodes: z.number().int().min(1).max(10_000).default(2_000),
maxScripts: z.number().int().min(1).max(2_000).default(500),
maxResources: z.number().int().min(1).max(10_000).default(2_000),
maxWorkers: z.number().int().min(1).max(5_000).default(500),
observationMs: z
.number()
.int()
.min(0)
.max(10_000)
.default(100)
.describe("Observation duration in milliseconds"),
includeScriptSources: z
.boolean()
.default(false)
.describe("Include bounded JavaScript source text"),
sourceCaptureApproved: z
.boolean()
.default(false)
.describe("Approve capturing bounded script source text"),
maxFrames: z
.number()
.int()
.min(1)
.max(1_000)
.default(200)
.describe("Maximum page frames"),
maxDomNodes: z
.number()
.int()
.min(1)
.max(10_000)
.default(2_000)
.describe("Maximum DOM nodes"),
maxScripts: z
.number()
.int()
.min(1)
.max(2_000)
.default(500)
.describe("Maximum scripts"),
maxResources: z
.number()
.int()
.min(1)
.max(10_000)
.default(2_000)
.describe("Maximum resources"),
maxWorkers: z
.number()
.int()
.min(1)
.max(5_000)
.default(500)
.describe("Maximum workers"),
maxScriptSourceBytes: z
.number()
.int()
.min(1)
.max(4 * 1_024 * 1_024)
.default(1_024 * 1_024),
.default(1_024 * 1_024)
.describe("Maximum size of one captured script source"),
maxTotalScriptSourceBytes: z
.number()
.int()
.min(1)
.max(16 * 1_024 * 1_024)
.default(4 * 1_024 * 1_024),
.default(4 * 1_024 * 1_024)
.describe("Maximum total captured script source size"),
}),
run: ({ args, options }) =>
logCliCommand(logger, "inspect-electron-page", async () => {
@ -174,39 +238,105 @@ const registerJavaScriptApplicationCommand = (
path: z.string().describe("Absolute ASAR or extracted application path"),
}),
options: z.object({
approved: z.boolean().default(false),
format: z.enum(["auto", "asar", "directory"]).default("auto"),
sourceMapReadApproved: z.boolean().default(false),
maxEntries: z.number().int().min(1).default(8_000),
approved: z
.boolean()
.default(false)
.describe("Approve reading the application artifact"),
format: z
.enum(["auto", "asar", "directory"])
.default("auto")
.describe("Application artifact format"),
sourceMapReadApproved: z
.boolean()
.default(false)
.describe(
"Approve reading local source maps referenced by the application",
),
maxEntries: z
.number()
.int()
.min(1)
.default(8_000)
.describe("Maximum artifact entries to inspect"),
maxTotalArtifactBytes: z
.number()
.int()
.min(1)
.default(512 * 1_024 * 1_024),
.default(512 * 1_024 * 1_024)
.describe("Maximum total uncompressed artifact content to inspect"),
maxArtifactEntryBytes: z
.number()
.int()
.min(1)
.default(128 * 1_024 * 1_024),
maxCompressionRatio: z.number().min(1).default(1_000),
maxDepth: z.number().int().min(1).default(64),
maxPathBytes: z.number().int().min(1).default(4_096),
maxTextFiles: z.number().int().min(1).default(5_000),
.default(128 * 1_024 * 1_024)
.describe("Maximum uncompressed content for one artifact entry"),
maxCompressionRatio: z
.number()
.min(1)
.default(1_000)
.describe(
"Maximum accepted compressed-to-uncompressed expansion ratio",
),
maxDepth: z
.number()
.int()
.min(1)
.default(64)
.describe("Maximum artifact directory depth"),
maxPathBytes: z
.number()
.int()
.min(1)
.default(4_096)
.describe("Maximum encoded artifact path length"),
maxTextFiles: z
.number()
.int()
.min(1)
.default(5_000)
.describe("Maximum text files to parse"),
maxTotalTextBytes: z
.number()
.int()
.min(1)
.default(128 * 1_024 * 1_024),
.default(128 * 1_024 * 1_024)
.describe("Maximum total text content to parse"),
maxTextFileBytes: z
.number()
.int()
.min(1)
.default(8 * 1_024 * 1_024),
maxAstNodes: z.number().int().min(1).default(2_000_000),
maxFindings: z.number().int().min(1).default(8_000),
maxModules: z.number().int().min(1).default(20_000),
maxSourceMapSources: z.number().int().min(1).default(5_000),
maxParseMilliseconds: z.number().int().min(1).default(30_000),
.default(8 * 1_024 * 1_024)
.describe("Maximum size of one parsed text file"),
maxAstNodes: z
.number()
.int()
.min(1)
.default(2_000_000)
.describe("Maximum JavaScript AST nodes to parse"),
maxFindings: z
.number()
.int()
.min(1)
.default(8_000)
.describe("Maximum static-analysis findings to return"),
maxModules: z
.number()
.int()
.min(1)
.default(20_000)
.describe("Maximum application modules to project"),
maxSourceMapSources: z
.number()
.int()
.min(1)
.default(5_000)
.describe("Maximum original sources from source maps"),
maxParseMilliseconds: z
.number()
.int()
.min(1)
.default(30_000)
.describe("Maximum JavaScript parsing time in milliseconds"),
}),
run: ({ args, options }) =>
logCliCommand(

View File

@ -79,8 +79,19 @@ export const registerEvidenceCommands = (
right: z.string().describe("Right Evidence bundle JSON path"),
}),
options: z.object({
offset: z.number().int().min(0).default(0),
limit: z.number().int().min(1).max(500).default(100),
offset: z
.number()
.int()
.min(0)
.default(0)
.describe("Zero-based changed-record offset"),
limit: z
.number()
.int()
.min(1)
.max(500)
.default(100)
.describe("Maximum changed records to return"),
}),
run: ({ args, options }) =>
logCliCommand(logger, "compare", async () => {

View File

@ -17,31 +17,74 @@ const investigationOptionsSchema = z.object({
.boolean()
.default(false)
.describe("Approve persistent workspace writes"),
workspaceName: z.string().trim().min(1).max(200).default("default"),
expectedRevision: z.number().int().min(1).optional(),
workspaceName: z
.string()
.trim()
.min(1)
.max(200)
.default("default")
.describe("Logical name recorded in the investigation workspace"),
expectedRevision: z
.number()
.int()
.min(1)
.optional()
.describe("Required current workspace revision for an atomic update"),
replayRunId: z
.string()
.regex(/^run_[a-f0-9]{64}$/u)
.optional()
.describe("Replay one verified complete workspace run without rescanning"),
maxEntries: z.number().int().min(1).max(50_000).default(10_000),
maxEntries: z
.number()
.int()
.min(1)
.max(50_000)
.default(10_000)
.describe("Maximum artifact entries to inspect per version"),
maxTotalBytes: z
.number()
.int()
.min(1)
.max(Number.MAX_SAFE_INTEGER)
.default(1_073_741_824),
.default(1_073_741_824)
.describe("Maximum total uncompressed artifact content to inspect"),
maxEntryBytes: z
.number()
.int()
.min(1)
.max(Number.MAX_SAFE_INTEGER)
.default(268_435_456),
pageSize: z.number().int().min(1).max(500).default(500),
changeLimit: z.number().int().min(1).max(500).default(500),
integrityPolicy: z.enum(["fail", "record-and-continue"]).default("fail"),
integrityContinueApproved: z.boolean().default(false),
maxIntegrityMismatches: z.number().int().min(1).max(100).default(10),
.default(268_435_456)
.describe("Maximum uncompressed content to inspect for one entry"),
pageSize: z
.number()
.int()
.min(1)
.max(500)
.default(500)
.describe("Maximum graph items loaded per investigation page"),
changeLimit: z
.number()
.int()
.min(1)
.max(500)
.default(500)
.describe("Maximum version changes to record"),
integrityPolicy: z
.enum(["fail", "record-and-continue"])
.default("fail")
.describe("Behavior when declared artifact integrity does not match"),
integrityContinueApproved: z
.boolean()
.default(false)
.describe("Approve continuing after recorded integrity mismatches"),
maxIntegrityMismatches: z
.number()
.int()
.min(1)
.max(100)
.default(10)
.describe("Maximum integrity mismatches to record before stopping"),
});
/** Register the persistent cross-version CLI workflow. */

View File

@ -58,17 +58,40 @@ export const registerPolicyCommands = (
cli.command(CLI_COMMANDS.policy, {
description: "Inspect, explain, or revoke unified local permission grants",
args: z.object({
action: z.enum(["status", "list", "explain", "revoke"]),
value: z.string().optional(),
action: z
.enum(["status", "list", "explain", "revoke"])
.describe("Permission-policy operation to perform"),
value: z
.string()
.optional()
.describe("Capability to explain or grant ID to revoke"),
}),
options: z.object({
root: z.string().optional(),
executable: z.string().optional(),
environmentNames: z.array(z.string()).default([]),
origins: z.array(z.string()).default([]),
network: z.enum(["none", "loopback", "external"]).default("none"),
mount: z.boolean().default(false),
write: z.boolean().default(false),
root: z.string().optional().describe("Filesystem root to authorize"),
executable: z
.string()
.optional()
.describe("Executable path to authorize"),
environmentNames: z
.array(z.string())
.default([])
.describe("Environment variable names the operation may read"),
origins: z
.array(z.string())
.default([])
.describe("Exact browser origins the operation may access"),
network: z
.enum(["none", "loopback", "external"])
.default("none")
.describe("Maximum network scope to authorize"),
mount: z
.boolean()
.default(false)
.describe("Authorize mounting an artifact filesystem"),
write: z
.boolean()
.default(false)
.describe("Explain the write form of the requested capability"),
yes: z
.boolean()
.default(false)

View File

@ -38,6 +38,7 @@ import {
import { ensureGeneratedFile } from "../scripts/lib/generated-file.mjs";
import {
createCliInventory,
cliCommandDescriptionIssues,
cliCommandOptionNames,
createProductCatalog,
serializeProductCatalog,
@ -161,6 +162,10 @@ describe("canonical product catalog", () => {
]);
});
it("describes every primary command argument and option", () => {
expect(cliCommandDescriptionIssues(createCli())).toEqual([]);
});
it("exposes the shared provider selector on every deep-analysis command", () => {
const cli = createCli();
for (const name of [