diff --git a/.agents/skills/reverse-engineer-anything/SKILL.md b/.agents/skills/reverse-engineer-anything/SKILL.md
index 39f9ae20b..f7e44e17a 100644
--- a/.agents/skills/reverse-engineer-anything/SKILL.md
+++ b/.agents/skills/reverse-engineer-anything/SKILL.md
@@ -31,6 +31,49 @@ Diagnose the observed blocker, continue useful supported work, and resume the
original task after repair. Client approval, shell PATH, registration and engine
availability have different remedies; avoid repeating setup for all of them.
+1. Diagnose without changing REA or client configuration. `npx` may download
+ the package and write to npm's cache. For Codex, run
+ `npx -y rea-agents@latest doctor --client codex --json`. Substitute the current
+ supported client: `claude_code`, `claude_desktop`, `codex`, `cursor`,
+ `gemini_cli`, `windsurf`, `devin`, `opencode`, `antigravity`, `copilot_cli`,
+ `commandcode`, `vscode`, `grok_build`, `omp`, or `grok_bot`. `grok_bot` has no local
+ file registration: doctor reports the Grok Bot chat step, and setup does not
+ write `mcp.json`. If the client is unknown, use `doctor --json` and
+ inspect its registration results before choosing a setup scope.
+2. Distinguish the reason. Missing, malformed, or stale registration needs a
+ scoped configuration repair. An aligned registration with no tools in the
+ active session needs a restart/reconnection; doctor cannot prove that the
+ current agent has connected. A missing provider affects only tasks requiring
+ that provider: static JavaScript inspection needs neither Hopper nor Ghidra,
+ and Android inspection has separate bring-your-own JADX/Java prerequisites.
+3. For a configuration repair, prepare the read-only plan:
+ `npx -y rea-agents@latest setup --client codex --dry-run --json`.
+ Use the current client's ID, show its exact proposed paths, backups, and
+ changes, and obtain approval before setup writes configuration or installs
+ Hopper. Setup normally installs the matching bundled skill too; include that
+ replacement in the reviewed plan. After approval, apply the same scope with
+ `npx -y rea-agents@latest setup --client codex --yes`. Add `--install-hopper`
+ only if that separate installation was needed and explicitly approved.
+4. Restart/reconnect the affected agent. Verify that REA tools actually appear
+ in the session, then resume the original investigation. If they remain
+ absent, inspect the client's MCP launch error rather than repeating setup.
+
+REA setup never installs or upgrades Node.js, npm, Homebrew, Java, Ghidra, IDA,
+JADX, Binwalk, or Unblob. Use existing prerequisites; do not install unrelated
+software to repair
+MCP registration. For an unsupported client, use manual stdio registration
+with a version-pinned `rea-agents` package or continue through the CLI.
+
+A concrete CLI fallback for an operator-supplied JavaScript tree or ASAR is:
+
+```bash
+npx -y rea-agents@latest analyze-javascript-application /absolute/path/to/app --json
+```
+
+No MCP registration or native engine is required. Read the returned Evidence,
+graph, limitations, and unknowns with the same care as an MCP result; the CLI
+returns the Evidence record directly. This fallback does not establish that
+MCP is configured. Native CLI tasks still require their selected engine.
Prefer an existing `rea` CLI. When none is installed and REA package acquisition
is within the authorized scope, use `npx -y rea-agents@latest` in its place.
Configuration repair starts with a read-only setup plan; show paths, backups and
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 8b0386626..8da32ee27 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -42,6 +42,7 @@ jobs:
tier: ${{ steps.plan.outputs.tier }}
qualification_run_id: ${{ steps.qualification.outputs.run_id }}
code: ${{ steps.plan.outputs.code }}
+ reverse_tools: ${{ steps.plan.outputs.reverse_tools }}
sqlite: ${{ steps.plan.outputs.sqlite }}
runtime: ${{ steps.plan.outputs.runtime }}
package: ${{ steps.plan.outputs.package }}
@@ -211,6 +212,24 @@ jobs:
overwrite: true
retention-days: 7
+ reverse-tools-quality:
+ name: Reverse tools coverage and mutation
+ needs: [changes, static]
+ if: needs.changes.outputs.reverse_tools == 'true'
+ runs-on: ubuntu-latest
+ timeout-minutes: 60
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: .nvmrc
+ cache: npm
+ - run: npm ci
+ - name: Run reverse tools tests with coverage
+ run: npm run test:coverage:reverse-tools
+ - name: Report reverse tools mutation results
+ run: npm run test:mutation:reverse-tools
+
build:
name: Build package
needs: changes
@@ -826,6 +845,7 @@ jobs:
changes,
static,
build,
+ reverse-tools-quality,
test-shard,
sqlite-runtime,
test,
diff --git a/.github/workflows/real-cutter.yml b/.github/workflows/real-cutter.yml
new file mode 100644
index 000000000..856777999
--- /dev/null
+++ b/.github/workflows/real-cutter.yml
@@ -0,0 +1,74 @@
+name: Real upstream Cutter verification
+
+on:
+ workflow_dispatch:
+ repository_dispatch:
+ types: [real-cutter-upstream]
+
+permissions:
+ contents: read
+
+jobs:
+ windows-native-artifact:
+ name: Build Windows bridge security boundary
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: .nvmrc
+ - name: Install the cross compiler used by the Windows native lane
+ run: |
+ sudo apt-get update
+ sudo apt-get install --no-install-recommends -y g++-mingw-w64-x86-64
+ - run: npm run build:windows-native
+ - uses: actions/upload-artifact@v4
+ with:
+ name: cutter-windows-native
+ path: native/windows/build/
+ if-no-files-found: error
+
+ cutter-upstream:
+ name: Cutter upstream smoke (${{ matrix.platform }})
+ needs: windows-native-artifact
+ runs-on: ${{ matrix.runner }}
+ timeout-minutes: 20
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - platform: linux
+ runner: [self-hosted, cutter-upstream, linux]
+ - platform: macOS
+ runner: [self-hosted, cutter-upstream, macOS]
+ - platform: Windows
+ runner: [self-hosted, cutter-upstream, Windows]
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version-file: .nvmrc
+ cache: npm
+ - run: npm ci
+ - if: matrix.platform == 'Windows'
+ uses: actions/download-artifact@v4
+ with:
+ name: cutter-windows-native
+ path: native/windows/build/
+ - if: matrix.platform == 'Windows'
+ run: npm run verify:windows-native:artifact
+ - if: matrix.platform == 'Windows'
+ run: npm run build:cached
+ - if: matrix.platform == 'Windows'
+ run: npm run verify:windows-native
+ - name: Verify host and real Cutter bridge
+ env:
+ EXPECTED_PLATFORM: ${{ matrix.platform }}
+ run: npm run verify:cutter
+ - uses: actions/upload-artifact@v4
+ if: always()
+ with:
+ name: cutter-upstream-${{ matrix.platform }}
+ path: cutter-smoke.json
+ if-no-files-found: ignore
+ retention-days: 30
diff --git a/.github/workflows/real-web-runtime.yml b/.github/workflows/real-web-runtime.yml
index 010032c87..0ceb9b057 100644
--- a/.github/workflows/real-web-runtime.yml
+++ b/.github/workflows/real-web-runtime.yml
@@ -28,7 +28,7 @@ jobs:
- uses: ./.github/actions/portable-build-cache
with:
build_cache_artifact: ${{ inputs.build_cache_artifact }}
- - run: npm run verify:browser:runtime
+ - run: env -u DBUS_SESSION_BUS_ADDRESS npm run verify:browser:runtime
env:
REA_BROWSER_EXECUTABLE: /usr/bin/google-chrome
REA_BROWSER_NO_SANDBOX: "true"
diff --git a/.gitignore b/.gitignore
index 0d006e13f..e4dc40814 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,6 +33,8 @@ codedb.snapshot
htmlcov/
.coverage
coverage/
+.stryker-tmp/
+reports/mutation/
.vitest/
.vitest-reports/
test-results.xml
diff --git a/README.md b/README.md
index 8b4291131..ec6928580 100644
--- a/README.md
+++ b/README.md
@@ -186,6 +186,11 @@ Static JavaScript and .NET inspection read the supplied files without running
the application. Runtime capture runs or interacts with the selected target
using your user permissions; each runtime guide describes its effects.
+For offline native-binary inspection, REA also supports caller-supplied GNU
+objdump and Rizin. See the [reverse-engineering tools guide](docs/reverse-engineering-tools.md)
+for provider setup, CLI commands, and Evidence limitations. REA does not install
+or update either provider.
+
Native formats and host support vary by provider. See
diff --git a/bridge/cutter/rea_bridge.py b/bridge/cutter/rea_bridge.py
new file mode 100644
index 000000000..473f1d5fe
--- /dev/null
+++ b/bridge/cutter/rea_bridge.py
@@ -0,0 +1,816 @@
+"""REA's experimental local bridge plugin for Cutter.
+
+Install this file in Cutter's user plugin directory. The bridge is REA-owned
+IPC, not an upstream Cutter transport. It binds to loopback and requires a
+per-process random token stored in a user-only descriptor file.
+"""
+
+from __future__ import annotations
+
+import json
+import hashlib
+import hmac
+import os
+import secrets
+import socket
+import stat
+import tempfile
+import threading
+import time
+import uuid
+from pathlib import Path
+from typing import Any
+
+if os.name == "nt":
+ import ctypes
+ from ctypes import wintypes
+
+import cutter
+
+try:
+ from PySide6.QtCore import QObject, Signal
+except ImportError:
+ from PySide2.QtCore import QObject, Signal
+
+
+MAX_FRAME_BYTES = 16 * 1024 * 1024
+SOCKET_TIMEOUT_SECONDS = 20
+AUTH_FRAME_TIMEOUT_SECONDS = 1
+MAX_DESCRIPTOR_BYTES = 64 * 1024
+MAX_COMPLETION_RECEIPTS = 64
+
+
+def _windows_api() -> tuple[Any, Any]:
+ kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
+ advapi32 = ctypes.WinDLL("advapi32", use_last_error=True)
+ kernel32.GetCurrentProcess.restype = wintypes.HANDLE
+ kernel32.LocalFree.argtypes = [wintypes.HLOCAL]
+ kernel32.LocalFree.restype = wintypes.HLOCAL
+ kernel32.CloseHandle.argtypes = [wintypes.HANDLE]
+ kernel32.CloseHandle.restype = wintypes.BOOL
+ kernel32.GetFileAttributesW.argtypes = [wintypes.LPCWSTR]
+ kernel32.GetFileAttributesW.restype = wintypes.DWORD
+ kernel32.CreateDirectoryW.argtypes = [wintypes.LPCWSTR, wintypes.LPVOID]
+ kernel32.CreateDirectoryW.restype = wintypes.BOOL
+ kernel32.CreateFileW.argtypes = [wintypes.LPCWSTR, wintypes.DWORD, wintypes.DWORD,
+ wintypes.LPVOID, wintypes.DWORD, wintypes.DWORD, wintypes.HANDLE]
+ kernel32.CreateFileW.restype = wintypes.HANDLE
+ kernel32.WriteFile.argtypes = [wintypes.HANDLE, wintypes.LPVOID, wintypes.DWORD,
+ ctypes.POINTER(wintypes.DWORD), wintypes.LPVOID]
+ kernel32.WriteFile.restype = wintypes.BOOL
+ kernel32.FlushFileBuffers.argtypes = [wintypes.HANDLE]
+ kernel32.FlushFileBuffers.restype = wintypes.BOOL
+ kernel32.MoveFileExW.argtypes = [wintypes.LPCWSTR, wintypes.LPCWSTR, wintypes.DWORD]
+ kernel32.MoveFileExW.restype = wintypes.BOOL
+ kernel32.DeleteFileW.argtypes = [wintypes.LPCWSTR]
+ kernel32.DeleteFileW.restype = wintypes.BOOL
+ advapi32.OpenProcessToken.argtypes = [wintypes.HANDLE, wintypes.DWORD, ctypes.POINTER(wintypes.HANDLE)]
+ advapi32.OpenProcessToken.restype = wintypes.BOOL
+ advapi32.GetTokenInformation.argtypes = [wintypes.HANDLE, ctypes.c_int, wintypes.LPVOID,
+ wintypes.DWORD, ctypes.POINTER(wintypes.DWORD)]
+ advapi32.GetTokenInformation.restype = wintypes.BOOL
+ advapi32.ConvertSidToStringSidW.argtypes = [wintypes.LPVOID, ctypes.POINTER(wintypes.LPWSTR)]
+ advapi32.ConvertSidToStringSidW.restype = wintypes.BOOL
+ advapi32.GetNamedSecurityInfoW.argtypes = [wintypes.LPWSTR, ctypes.c_int, wintypes.DWORD,
+ ctypes.POINTER(wintypes.LPVOID), ctypes.POINTER(wintypes.LPVOID),
+ ctypes.POINTER(wintypes.LPVOID), ctypes.POINTER(wintypes.LPVOID),
+ ctypes.POINTER(wintypes.LPVOID)]
+ advapi32.GetNamedSecurityInfoW.restype = wintypes.DWORD
+ advapi32.GetSecurityDescriptorControl.argtypes = [wintypes.LPVOID, ctypes.POINTER(wintypes.WORD),
+ ctypes.POINTER(wintypes.DWORD)]
+ advapi32.GetSecurityDescriptorControl.restype = wintypes.BOOL
+ advapi32.EqualSid.argtypes = [wintypes.LPVOID, wintypes.LPVOID]
+ advapi32.EqualSid.restype = wintypes.BOOL
+ advapi32.GetAclInformation.argtypes = [wintypes.LPVOID, wintypes.LPVOID, wintypes.DWORD, ctypes.c_int]
+ advapi32.GetAclInformation.restype = wintypes.BOOL
+ advapi32.GetAce.argtypes = [wintypes.LPVOID, wintypes.DWORD, ctypes.POINTER(wintypes.LPVOID)]
+ advapi32.GetAce.restype = wintypes.BOOL
+ advapi32.IsValidSid.argtypes = [wintypes.LPVOID]
+ advapi32.IsValidSid.restype = wintypes.BOOL
+ advapi32.GetLengthSid.argtypes = [wintypes.LPVOID]
+ advapi32.GetLengthSid.restype = wintypes.DWORD
+ advapi32.ConvertStringSecurityDescriptorToSecurityDescriptorW.argtypes = [
+ wintypes.LPCWSTR, wintypes.DWORD, ctypes.POINTER(wintypes.LPVOID), ctypes.POINTER(wintypes.DWORD),
+ ]
+ advapi32.ConvertStringSecurityDescriptorToSecurityDescriptorW.restype = wintypes.BOOL
+ return kernel32, advapi32
+
+
+def _windows_user_sid_text() -> str:
+ kernel32, advapi32 = _windows_api()
+ token = wintypes.HANDLE()
+ if not advapi32.OpenProcessToken(kernel32.GetCurrentProcess(), 0x0008, ctypes.byref(token)):
+ raise ctypes.WinError(ctypes.get_last_error())
+ try:
+ required = wintypes.DWORD()
+ advapi32.GetTokenInformation(token, 1, None, 0, ctypes.byref(required))
+ if required.value == 0:
+ raise ctypes.WinError(ctypes.get_last_error())
+ buffer = ctypes.create_string_buffer(required.value)
+ if not advapi32.GetTokenInformation(token, 1, buffer, required, ctypes.byref(required)):
+ raise ctypes.WinError(ctypes.get_last_error())
+
+ class SID_AND_ATTRIBUTES(ctypes.Structure):
+ _fields_ = [("Sid", wintypes.LPVOID), ("Attributes", wintypes.DWORD)]
+
+ class TOKEN_USER(ctypes.Structure):
+ _fields_ = [("User", SID_AND_ATTRIBUTES)]
+
+ sid = ctypes.cast(buffer, ctypes.POINTER(TOKEN_USER)).contents.User.Sid
+ text = wintypes.LPWSTR()
+ if not advapi32.ConvertSidToStringSidW(sid, ctypes.byref(text)):
+ raise ctypes.WinError(ctypes.get_last_error())
+ try:
+ return text.value
+ finally:
+ kernel32.LocalFree(text)
+ finally:
+ kernel32.CloseHandle(token)
+
+
+def _verify_windows_private_path(path: Path, directory: bool) -> None:
+ kernel32, advapi32 = _windows_api()
+ attributes = kernel32.GetFileAttributesW(str(path))
+ if attributes == 0xFFFFFFFF:
+ raise ctypes.WinError(ctypes.get_last_error())
+ if attributes & 0x400 or bool(attributes & 0x10) != directory:
+ raise RuntimeError("Cutter bridge path is a reparse point or has the wrong object type")
+
+ class ACL_SIZE_INFORMATION(ctypes.Structure):
+ _fields_ = [("AceCount", wintypes.DWORD), ("AclBytesInUse", wintypes.DWORD), ("AclBytesFree", wintypes.DWORD)]
+
+ class ACE_HEADER(ctypes.Structure):
+ _fields_ = [("AceType", wintypes.BYTE), ("AceFlags", wintypes.BYTE), ("AceSize", wintypes.WORD)]
+
+ class SID_HEADER(ctypes.Structure):
+ _fields_ = [("Revision", wintypes.BYTE), ("SubAuthorityCount", wintypes.BYTE),
+ ("IdentifierAuthority", wintypes.BYTE * 6)]
+
+ class ACCESS_ALLOWED_ACE(ctypes.Structure):
+ _fields_ = [("Header", ACE_HEADER), ("Mask", wintypes.DWORD), ("SidStart", wintypes.DWORD)]
+
+ owner = wintypes.LPVOID()
+ dacl = wintypes.LPVOID()
+ descriptor = wintypes.LPVOID()
+ result = advapi32.GetNamedSecurityInfoW(
+ str(path), 1, 0x1 | 0x4, ctypes.byref(owner), None, ctypes.byref(dacl), None, ctypes.byref(descriptor),
+ )
+ if result != 0:
+ raise ctypes.WinError(result)
+ try:
+ control = wintypes.WORD()
+ revision = wintypes.DWORD()
+ if not advapi32.GetSecurityDescriptorControl(descriptor, ctypes.byref(control), ctypes.byref(revision)):
+ raise ctypes.WinError(ctypes.get_last_error())
+ if not control.value & 0x1000 or not dacl:
+ raise RuntimeError("Cutter bridge ACL must be present and protected from inheritance")
+
+ token = wintypes.HANDLE()
+ if not advapi32.OpenProcessToken(kernel32.GetCurrentProcess(), 0x0008, ctypes.byref(token)):
+ raise ctypes.WinError(ctypes.get_last_error())
+ try:
+ required = wintypes.DWORD()
+ advapi32.GetTokenInformation(token, 1, None, 0, ctypes.byref(required))
+ token_user = ctypes.create_string_buffer(required.value)
+ if not advapi32.GetTokenInformation(token, 1, token_user, required, ctypes.byref(required)):
+ raise ctypes.WinError(ctypes.get_last_error())
+
+ class SID_AND_ATTRIBUTES(ctypes.Structure):
+ _fields_ = [("Sid", wintypes.LPVOID), ("Attributes", wintypes.DWORD)]
+
+ class TOKEN_USER(ctypes.Structure):
+ _fields_ = [("User", SID_AND_ATTRIBUTES)]
+
+ current_sid = ctypes.cast(token_user, ctypes.POINTER(TOKEN_USER)).contents.User.Sid
+ if not advapi32.EqualSid(owner, current_sid):
+ raise RuntimeError("Cutter bridge path owner does not match the current user")
+ finally:
+ kernel32.CloseHandle(token)
+
+ info = ACL_SIZE_INFORMATION()
+ if not advapi32.GetAclInformation(dacl, ctypes.byref(info), ctypes.sizeof(info), 2) or info.AceCount != 2:
+ raise RuntimeError("Cutter bridge ACL must allow only the current user and SYSTEM")
+ expected_user = _windows_user_sid_text()
+ user_allowed = False
+ system_allowed = False
+ for index in range(info.AceCount):
+ ace_pointer = wintypes.LPVOID()
+ if not advapi32.GetAce(dacl, index, ctypes.byref(ace_pointer)):
+ raise ctypes.WinError(ctypes.get_last_error())
+ header = ctypes.cast(ace_pointer, ctypes.POINTER(ACE_HEADER)).contents
+ if header.AceType != 0 or header.AceSize < ctypes.sizeof(ACE_HEADER) + ctypes.sizeof(wintypes.DWORD) + ctypes.sizeof(SID_HEADER):
+ raise RuntimeError("Cutter bridge ACL contains a malformed or unsupported ACE")
+ ace_address = ctypes.cast(ace_pointer, ctypes.c_void_p).value
+ if ace_address is None:
+ raise RuntimeError("Cutter bridge ACL returned a null ACE")
+ sid_pointer = ctypes.c_void_p(ace_address + ctypes.sizeof(ACE_HEADER) + ctypes.sizeof(wintypes.DWORD))
+ sid_header = ctypes.cast(sid_pointer, ctypes.POINTER(SID_HEADER)).contents
+ sid_size = ctypes.sizeof(SID_HEADER) + sid_header.SubAuthorityCount * ctypes.sizeof(wintypes.DWORD)
+ if sid_size > header.AceSize - ctypes.sizeof(ACE_HEADER) - ctypes.sizeof(wintypes.DWORD) or not advapi32.IsValidSid(sid_pointer) or advapi32.GetLengthSid(sid_pointer) != sid_size:
+ raise RuntimeError("Cutter bridge ACL contains an invalid SID")
+ ace = ctypes.cast(ace_pointer, ctypes.POINTER(ACCESS_ALLOWED_ACE)).contents
+ sid_text = wintypes.LPWSTR()
+ if ace.Mask != 0x001F01FF or ace.Header.AceFlags != 0x03:
+ raise RuntimeError("Cutter bridge ACL contains an unsupported ACE")
+ if not advapi32.ConvertSidToStringSidW(sid_pointer, ctypes.byref(sid_text)):
+ raise ctypes.WinError(ctypes.get_last_error())
+ try:
+ if sid_text.value == expected_user:
+ user_allowed = True
+ elif sid_text.value == "S-1-5-18":
+ system_allowed = True
+ else:
+ raise RuntimeError("Cutter bridge ACL allows an unrelated principal")
+ finally:
+ kernel32.LocalFree(sid_text)
+ if not user_allowed or not system_allowed:
+ raise RuntimeError("Cutter bridge ACL must allow only the current user and SYSTEM")
+ finally:
+ kernel32.LocalFree(descriptor)
+
+
+def _windows_security_attributes() -> tuple[Any, Any]:
+ kernel32, advapi32 = _windows_api()
+ sid = _windows_user_sid_text()
+ sddl = f"O:{sid}G:{sid}D:P(A;OICI;FA;;;{sid})(A;OICI;FA;;;SY)"
+ descriptor = wintypes.LPVOID()
+ if not advapi32.ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl, 1, ctypes.byref(descriptor), None):
+ raise ctypes.WinError(ctypes.get_last_error())
+
+ class SECURITY_ATTRIBUTES(ctypes.Structure):
+ _fields_ = [("nLength", wintypes.DWORD), ("lpSecurityDescriptor", wintypes.LPVOID), ("bInheritHandle", wintypes.BOOL)]
+
+ attributes = SECURITY_ATTRIBUTES(ctypes.sizeof(SECURITY_ATTRIBUTES), descriptor, False)
+ return kernel32, (descriptor, attributes)
+
+
+def _ensure_windows_bridge_directory(directory: Path) -> None:
+ directory.parent.mkdir(parents=True, exist_ok=True)
+ if not directory.exists():
+ kernel32, (descriptor, attributes) = _windows_security_attributes()
+ try:
+ if not kernel32.CreateDirectoryW(str(directory), ctypes.byref(attributes)):
+ error = ctypes.get_last_error()
+ if error != 183:
+ raise ctypes.WinError(error)
+ finally:
+ kernel32.LocalFree(descriptor)
+ _verify_windows_private_path(directory, True)
+
+
+def _ensure_posix_bridge_directory(directory: Path) -> None:
+ directory.mkdir(mode=0o700, parents=True, exist_ok=True)
+ if not hasattr(os, "O_NOFOLLOW") or not hasattr(os, "O_DIRECTORY"):
+ raise RuntimeError("Cutter bridge directory requires no-follow directory handles")
+ descriptor = os.open(directory, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)
+ try:
+ directory_info = os.fstat(descriptor)
+ if directory_info.st_uid != os.getuid():
+ raise RuntimeError("Cutter bridge directory must be owned by the current user")
+ os.fchmod(descriptor, 0o700)
+ directory_info = os.fstat(descriptor)
+ if stat.S_IMODE(directory_info.st_mode) != 0o700:
+ raise RuntimeError("Cutter bridge directory must have mode 0700")
+ finally:
+ os.close(descriptor)
+
+
+def _write_windows_descriptor(directory: Path, destination: Path, payload: bytes) -> None:
+ if len(payload) > MAX_DESCRIPTOR_BYTES:
+ raise RuntimeError("Cutter bridge descriptor exceeds its byte limit")
+ kernel32, (security_descriptor, attributes) = _windows_security_attributes()
+ temporary = directory / f".{destination.name}.{uuid.uuid4().hex}.tmp"
+ handle = wintypes.HANDLE()
+ try:
+ handle = kernel32.CreateFileW(str(temporary), 0x40000000 | 0x00020000 | 0x00040000,
+ 0x1 | 0x2 | 0x4, ctypes.byref(attributes), 1, 0x80, None)
+ if handle == wintypes.HANDLE(-1).value:
+ raise ctypes.WinError(ctypes.get_last_error())
+ _verify_windows_private_path(temporary, False)
+ if payload:
+ data = ctypes.create_string_buffer(payload)
+ written = wintypes.DWORD()
+ if not kernel32.WriteFile(handle, data, len(payload), ctypes.byref(written), None) or written.value != len(payload):
+ raise ctypes.WinError(ctypes.get_last_error())
+ if not kernel32.FlushFileBuffers(handle):
+ raise ctypes.WinError(ctypes.get_last_error())
+ kernel32.CloseHandle(handle)
+ handle = wintypes.HANDLE()
+ if not kernel32.MoveFileExW(str(temporary), str(destination), 0x1 | 0x8):
+ raise ctypes.WinError(ctypes.get_last_error())
+ finally:
+ if handle and handle != wintypes.HANDLE(-1).value:
+ kernel32.CloseHandle(handle)
+ kernel32.DeleteFileW(str(temporary))
+ kernel32.LocalFree(security_descriptor)
+
+
+def _bridge_directory() -> Path:
+ configured = os.environ.get("REA_CUTTER_BRIDGE_DIR")
+ if configured:
+ return Path(configured).expanduser()
+ local_app_data = os.environ.get("LOCALAPPDATA")
+ if os.name == "nt" and local_app_data:
+ return Path(local_app_data) / "REA" / "CutterBridge"
+ return Path.home() / ".cache" / "rea" / "cutter-bridge"
+
+
+class _RequestDispatcher(QObject):
+ dispatch = Signal(object, object)
+
+ def __init__(self) -> None:
+ super().__init__()
+ self.dispatch.connect(self._handle)
+ self.generation = 0
+ self.completed_commands: dict[str, str] = {}
+ self._receipt_lock = threading.Lock()
+ self.current_file = self._read_current_file()
+ try:
+ self.cutter_version = str(cutter.core().getVersionInformation())
+ except Exception:
+ self.cutter_version = None
+
+ @staticmethod
+ def _read_current_file() -> str | None:
+ try:
+ value = cutter.core().currentlyOpenFile()
+ return str(value) if value else None
+ except Exception:
+ return None
+
+ def _handle(self, request: dict[str, Any], reply: dict[str, Any]) -> None:
+ kind = request.get("kind")
+ request_id = request.get("request_id")
+ if kind in ("command", "ack"):
+ try:
+ valid_request_id = (
+ isinstance(request_id, str)
+ and str(uuid.UUID(request_id)) == request_id.lower()
+ )
+ except (ValueError, AttributeError):
+ valid_request_id = False
+ if not valid_request_id:
+ reply.update({
+ "ok": False,
+ "error": "invalid-command-request-id",
+ "execution_state": "not_started",
+ })
+ request["_event"].set()
+ return
+ if kind == "ack":
+ with self._receipt_lock:
+ acknowledged = self.completed_commands.pop(request_id, None) is not None
+ reply.update({"ok": True, "acknowledged": acknowledged})
+ request["_event"].set()
+ return
+ if request["_cancelled"].is_set():
+ reply.update({
+ "ok": False,
+ "error": "request-cancelled-before-execution",
+ "execution_state": "not_started",
+ })
+ if kind == "command":
+ self._record_command_completion(request_id, "not_started")
+ request["_event"].set()
+ return
+ self._refresh_document_identity()
+ if kind == "status":
+ with self._receipt_lock:
+ completed_commands = [
+ {"request_id": receipt_id, "execution_state": state}
+ for receipt_id, state in self.completed_commands.items()
+ ]
+ reply.update({
+ "ok": True,
+ "session_id": request.get("session_id"),
+ "current_file": self.current_file,
+ "document_generation": self.generation,
+ "cutter_version": self.cutter_version,
+ "identity_status": "partial",
+ "completed_commands": completed_commands,
+ })
+ request["_event"].set()
+ return
+ if kind != "command":
+ reply.update({"ok": False, "error": "unsupported-request-kind"})
+ request["_event"].set()
+ return
+ if request.get("expected_generation") != self.generation:
+ reply.update({
+ "ok": False,
+ "error": "active-document-generation-changed",
+ "execution_state": "not_started",
+ })
+ self._record_command_completion(request_id, "not_started")
+ request["_event"].set()
+ return
+ command = request.get("command")
+ if not isinstance(command, str) or not command.strip():
+ reply.update({
+ "ok": False,
+ "error": "invalid-command",
+ "execution_state": "not_started",
+ })
+ self._record_command_completion(request_id, "not_started")
+ request["_event"].set()
+ return
+ reply["execution_state"] = "running"
+ try:
+ output = cutter.cmdj(command) if request.get("json") is True else cutter.cmd(command)
+ command_error = None
+ except Exception as error:
+ output = None
+ command_error = str(error)
+ self._refresh_document_identity()
+ if command_error is None:
+ reply.update({
+ "ok": True,
+ "output": output,
+ "current_file": self.current_file,
+ "document_generation": self.generation,
+ "cutter_version": self.cutter_version,
+ "identity_status": "partial",
+ "execution_state": "complete",
+ })
+ self._record_command_completion(request_id, "complete")
+ else:
+ reply.update({
+ "ok": False,
+ "error": command_error,
+ "current_file": self.current_file,
+ "document_generation": self.generation,
+ "cutter_version": self.cutter_version,
+ "identity_status": "partial",
+ "execution_state": "unknown",
+ "message": "The command raised an exception; it may have produced partial effects. Do not retry automatically.",
+ })
+ self._record_command_completion(request_id, "failed")
+ request["_event"].set()
+
+ def _record_command_completion(
+ self, request_id: str, execution_state: str
+ ) -> None:
+ with self._receipt_lock:
+ if request_id in self.completed_commands or len(self.completed_commands) < MAX_COMPLETION_RECEIPTS:
+ self.completed_commands[request_id] = execution_state
+
+ def can_accept_command(self) -> bool:
+ with self._receipt_lock:
+ return len(self.completed_commands) < MAX_COMPLETION_RECEIPTS
+
+ def _refresh_document_identity(self) -> None:
+ current_file = self._read_current_file()
+ if current_file != self.current_file:
+ self.current_file = current_file
+ self.generation += 1
+
+
+class _BridgeServer(threading.Thread):
+ def __init__(self, dispatcher: _RequestDispatcher) -> None:
+ super().__init__(name="rea-cutter-bridge", daemon=True)
+ self.dispatcher = dispatcher
+ self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ self.server.bind(("127.0.0.1", 0))
+ self.server.listen(8)
+ self.server.settimeout(0.5)
+ self.port = self.server.getsockname()[1]
+ self.session_id = str(uuid.uuid4())
+ self.token = secrets.token_urlsafe(32)
+ self.poisoned = False
+ self.connection_ordinal = 0
+ self.directory = _bridge_directory()
+ self.descriptor = self.directory / f"cutter-{os.getpid()}-{self.session_id}.json"
+
+ def publish(self) -> None:
+ if os.name == "nt":
+ _ensure_windows_bridge_directory(self.directory)
+ else:
+ _ensure_posix_bridge_directory(self.directory)
+ if os.name == "nt" and (self.directory.is_symlink() or not self.directory.is_dir()):
+ raise RuntimeError("Cutter bridge directory must be a real directory")
+ payload = {
+ "session_id": self.session_id,
+ "pid": os.getpid(),
+ "host": "127.0.0.1",
+ "port": self.port,
+ "token": self.token,
+ "document_generation": self.dispatcher.generation,
+ "current_file": self.dispatcher.current_file,
+ "cutter_version": self.dispatcher.cutter_version,
+ "identity_status": "partial",
+ }
+ if os.name == "nt":
+ _write_windows_descriptor(self.directory, self.descriptor, json.dumps(payload).encode("utf-8"))
+ return
+ descriptor, temporary_path = tempfile.mkstemp(prefix=f".{self.descriptor.name}.", dir=self.directory)
+ try:
+ os.fchmod(descriptor, 0o600)
+ descriptor_info = os.fstat(descriptor)
+ if descriptor_info.st_uid != os.getuid() or stat.S_IMODE(descriptor_info.st_mode) & 0o077:
+ raise RuntimeError("Cutter bridge descriptor must be owned by the current user with mode 0600")
+ with os.fdopen(descriptor, "w", encoding="utf-8") as stream:
+ stream.write(json.dumps(payload))
+ stream.flush()
+ os.fsync(stream.fileno())
+ os.replace(temporary_path, self.descriptor)
+ except Exception:
+ try:
+ os.close(descriptor)
+ except OSError:
+ pass
+ try:
+ os.unlink(temporary_path)
+ except OSError:
+ pass
+ raise
+
+ def run(self) -> None:
+ try:
+ self.publish()
+ except Exception:
+ self.server.close()
+ return
+ while True:
+ try:
+ connection, _ = self.server.accept()
+ except socket.timeout:
+ if getattr(self, "stopping", False):
+ break
+ continue
+ except OSError:
+ break
+ self.connection_ordinal += 1
+ connection_ordinal = self.connection_ordinal
+ with connection:
+ connection.settimeout(AUTH_FRAME_TIMEOUT_SECONDS)
+ authenticated = False
+ try:
+ hello = self._receive_frame(connection)
+ if not isinstance(hello, dict) or hello.get("kind") != "rea-auth-v2" or not isinstance(hello.get("nonce"), str) or len(hello["nonce"]) != 64:
+ self._send(connection, {"ok": False, "error": "authentication-failed"})
+ continue
+ client_nonce = hello["nonce"]
+ server_nonce = f"{connection_ordinal:016x}{secrets.token_hex(24)}"
+ server_proof = hmac.new(self.token.encode(), f"rea-cutter-server-v2:{self.session_id}:{client_nonce}:{server_nonce}".encode(), hashlib.sha256).hexdigest()
+ self._send(connection, {"session_id": self.session_id, "nonce": server_nonce, "proof": server_proof})
+ authenticated_frame = self._receive_frame(connection)
+ request_bytes = authenticated_frame.get("request") if isinstance(authenticated_frame, dict) else None
+ expected_proof = hmac.new(self.token.encode(), f"rea-cutter-client-v2:{self.session_id}:{client_nonce}:{server_nonce}:{request_bytes}".encode(), hashlib.sha256).hexdigest()
+ if not isinstance(request_bytes, str) or not isinstance(authenticated_frame.get("proof"), str) or not hmac.compare_digest(authenticated_frame["proof"], expected_proof):
+ self._send(connection, {"ok": False, "error": "authentication-failed"})
+ continue
+ request = json.loads(request_bytes)
+ if not isinstance(request, dict) or request.get("session_id") != self.session_id:
+ self._send(connection, {"ok": False, "error": "authentication-failed"})
+ continue
+ authenticated = True
+ connection.settimeout(SOCKET_TIMEOUT_SECONDS)
+ if self.poisoned and request.get("kind") not in ("status", "ack"):
+ self._send_authenticated(connection, {
+ "ok": False,
+ "error": "session-poisoned-after-ui-timeout",
+ "execution_state": "not_started",
+ }, client_nonce, server_nonce)
+ continue
+ if (
+ request.get("kind") == "command"
+ and not self.dispatcher.can_accept_command()
+ ):
+ self._send_authenticated(connection, {
+ "ok": False,
+ "error": "completion-receipt-capacity-exceeded",
+ "execution_state": "not_started",
+ }, client_nonce, server_nonce)
+ continue
+ request["session_id"] = self.session_id
+ event = threading.Event()
+ cancelled = threading.Event()
+ reply: dict[str, Any] = {}
+ request["_event"] = event
+ request["_cancelled"] = cancelled
+ self.dispatcher.dispatch.emit(request, reply)
+ if not event.wait(SOCKET_TIMEOUT_SECONDS):
+ cancelled.set()
+ self.poisoned = True
+ self._send_authenticated(connection, {
+ "ok": False,
+ "error": "ui-thread-request-timeout",
+ "execution_state": "unknown",
+ "message": "The UI operation may still complete after this timeout; restart Cutter before issuing further commands.",
+ }, client_nonce, server_nonce)
+ else:
+ if request.get("kind") == "status" and reply.get("ok") is True:
+ reply["completed_connection"] = f"{connection_ordinal - 1:016x}"
+ self._send_authenticated(connection, reply, client_nonce, server_nonce)
+ except socket.timeout:
+ if authenticated:
+ self._send_authenticated(connection, {
+ "ok": False,
+ "error": "request-timeout",
+ "execution_state": "unknown",
+ "message": "The command may have produced partial or persistent effects. Do not retry automatically.",
+ }, client_nonce, server_nonce)
+ except Exception as error:
+ self._send(connection, {"ok": False, "error": str(error)})
+ self.server.close()
+ try:
+ self.descriptor.unlink(missing_ok=True)
+ except OSError:
+ pass
+
+ @staticmethod
+ def _receive_frame(connection: socket.socket) -> dict[str, Any]:
+ data = bytearray()
+ deadline = time.monotonic() + AUTH_FRAME_TIMEOUT_SECONDS
+ while b"\n" not in data:
+ if len(data) >= MAX_FRAME_BYTES:
+ raise ValueError("frame-limit-exceeded")
+ remaining = deadline - time.monotonic()
+ if remaining <= 0:
+ raise socket.timeout("authentication frame timed out")
+ connection.settimeout(remaining)
+ part = connection.recv(min(65536, MAX_FRAME_BYTES + 1 - len(data)))
+ if not part:
+ raise ValueError("incomplete authentication frame")
+ data.extend(part)
+ line, remainder = bytes(data).split(b"\n", 1)
+ if len(line) > MAX_FRAME_BYTES or remainder:
+ raise ValueError("invalid framed request")
+ value = json.loads(line)
+ return value if isinstance(value, dict) else {}
+
+ def _send_authenticated(self, connection: socket.socket, value: dict[str, Any], client_nonce: str, server_nonce: str) -> None:
+ encoded = self._encode_authenticated_response(value, client_nonce, server_nonce)
+ if encoded is None:
+ value = self._truncated_response(value)
+ encoded = self._encode_authenticated_response(value, client_nonce, server_nonce)
+ if encoded is None:
+ # This fixed-size fallback is checked through the same signed framing path.
+ value = {
+ "ok": False,
+ "error": "response-limit-exceeded",
+ "execution_state": value.get("execution_state", "unknown"),
+ "output": None,
+ "output_truncated": True,
+ "message": "The result exceeded the bridge frame limit; command completion state is preserved.",
+ }
+ encoded = self._encode_authenticated_response(value, client_nonce, server_nonce)
+ if encoded is None:
+ return
+ try:
+ connection.sendall(encoded)
+ except OSError:
+ pass
+
+ def _encode_authenticated_response(self, value: dict[str, Any], client_nonce: str, server_nonce: str) -> bytes | None:
+ if not _within_response_limit(value):
+ return None
+ prefix = b'{"response":"'
+ suffix = b'","proof":"' + (b"0" * 64) + b'"}\n'
+ frame_size = len(prefix) + len(suffix)
+ response_parts: list[bytes] = []
+ escaped_parts: list[bytes] = []
+ for fragment in json.JSONEncoder(ensure_ascii=False, separators=(",", ":")).iterencode(value):
+ raw = fragment.encode("utf-8")
+ escaped = json.dumps(fragment, ensure_ascii=False)[1:-1].encode("utf-8")
+ frame_size += len(escaped)
+ if frame_size > MAX_FRAME_BYTES:
+ return None
+ response_parts.append(raw)
+ escaped_parts.append(escaped)
+ proof = hmac.new(self.token.encode(), digestmod=hashlib.sha256)
+ proof.update(f"rea-cutter-response-v2:{self.session_id}:{client_nonce}:{server_nonce}:".encode())
+ for part in response_parts:
+ proof.update(part)
+ return prefix + b"".join(escaped_parts) + b'","proof":"' + proof.hexdigest().encode() + b'"}\n'
+
+ @staticmethod
+ def _truncated_response(value: dict[str, Any]) -> dict[str, Any]:
+ response: dict[str, Any] = {
+ "ok": value.get("ok") is True,
+ "output": None,
+ "execution_state": value.get("execution_state", "unknown"),
+ "output_truncated": True,
+ "message": "The command result exceeded the bridge frame limit; completion state is preserved and output was omitted.",
+ }
+ for key in ("current_file", "document_generation", "cutter_version", "identity_status"):
+ if key in value:
+ response[key] = value[key]
+ if value.get("ok") is not True and isinstance(value.get("error"), str):
+ response["error"] = value["error"]
+ return response
+
+ @staticmethod
+ def _send(connection: socket.socket, value: dict[str, Any]) -> None:
+ if not _within_response_limit(value):
+ value = {
+ "ok": False,
+ "error": "response-limit-exceeded",
+ "execution_state": value.get("execution_state", "unknown"),
+ "output_truncated": True,
+ "message": "The command completed or may have completed, but its result exceeded the bridge response limit. Do not retry automatically.",
+ }
+ parts: list[bytes] = []
+ encoded_size = 0
+ for part in json.JSONEncoder(ensure_ascii=False).iterencode(value):
+ encoded_part = part.encode("utf-8")
+ encoded_size += len(encoded_part)
+ if encoded_size > MAX_FRAME_BYTES:
+ fallback = {
+ "ok": False,
+ "error": "response-limit-exceeded",
+ "execution_state": value.get("execution_state", "unknown"),
+ "output_truncated": True,
+ "message": "The command completed or may have completed, but its result exceeded the bridge response limit. Do not retry automatically.",
+ }
+ parts = [json.dumps(fallback, separators=(",", ":")).encode("utf-8")]
+ break
+ parts.append(encoded_part)
+ encoded = b"".join(parts)
+ try:
+ connection.sendall(encoded + b"\n")
+ except OSError:
+ # A disconnected caller must not terminate the bridge thread or
+ # prevent later commands from being served.
+ pass
+
+ def stop(self) -> None:
+ self.stopping = True
+ try:
+ self.server.close()
+ except OSError:
+ pass
+
+
+class ReaCutterPlugin(cutter.CutterPlugin):
+ name = "REA Local Bridge"
+ description = "Local REA analysis bridge for the active Cutter session"
+ version = "1.0"
+ author = "REA"
+
+ def __init__(self) -> None:
+ super().__init__()
+ self._dispatcher: _RequestDispatcher | None = None
+ self._bridge: _BridgeServer | None = None
+
+ def setupPlugin(self) -> None:
+ return
+
+ def setupInterface(self, main: Any) -> None:
+ self._dispatcher = _RequestDispatcher()
+ self._bridge = _BridgeServer(self._dispatcher)
+ self._bridge.start()
+
+ def terminate(self) -> None:
+ if self._bridge is not None:
+ self._bridge.stop()
+ self._bridge.join(timeout=2)
+ self._bridge = None
+ self._dispatcher = None
+
+
+def create_cutter_plugin() -> ReaCutterPlugin:
+ return ReaCutterPlugin()
+
+
+def _within_response_limit(value: Any) -> bool:
+ pending = [value]
+ seen: set[int] = set()
+ size = 0
+ while pending:
+ current = pending.pop()
+ if isinstance(current, str):
+ size += 2
+ if current.isascii():
+ size += len(current)
+ size += current.count('"') + current.count("\\")
+ size += sum(5 for character in current if ord(character) < 0x20)
+ else:
+ size += 4 * len(current)
+ size += current.count('"') + current.count("\\")
+ size += sum(5 for character in current if ord(character) < 0x20)
+ elif isinstance(current, dict):
+ identity = id(current)
+ if identity in seen:
+ return False
+ seen.add(identity)
+ size += 2 + 4 * len(current)
+ if size > MAX_FRAME_BYTES:
+ return False
+ pending.extend(current.keys())
+ pending.extend(current.values())
+ elif isinstance(current, (list, tuple)):
+ identity = id(current)
+ if identity in seen:
+ return False
+ seen.add(identity)
+ size += 2 + 2 * len(current)
+ if size > MAX_FRAME_BYTES:
+ return False
+ pending.extend(current)
+ else:
+ size += 16
+ if size > MAX_FRAME_BYTES:
+ return False
+ return True
diff --git a/docs/architecture.mermaid b/docs/architecture.mermaid
index 753c57024..90dd41ab1 100644
--- a/docs/architecture.mermaid
+++ b/docs/architecture.mermaid
@@ -38,6 +38,10 @@ graph TD
JS_CODEC["src/javascript/sourceMaps/
pinned upstream map codec
owned heap-limited Node process, complete point evidence"]
INSPECTOR["src/inspector/
passive V8 Inspector discovery and capture
script and target locations"]
DOTNET["src/dotnet/
Managed static metadata provider
PE/CLI metadata inventory, members, IL"]
+ OBJDUMP["src/objdump/
GNU objdump command profile"]
+ RIZIN["src/rizin/
Static command profile and persistent debugger sessions"]
+ GDB["src/gdb/
Persistent GDB/MI sessions
owned process lifecycle and target state"]
+
FIRMWARE["src/firmware/
Binwalk / Unblob adapters
owned processes, report normalization, private extraction"]
ANDROID["src/android/
Static APK adapter
owned SDK stdio, pinned headless JADX, normalization"]
REFERENCE["src/reference/
Filesystem reference-source reader
bounded traversal, validation, errors"]
@@ -50,7 +54,7 @@ graph TD
end
subgraph "Contracts Layer"
- CONTRACTS["src/contracts/
Caller-visible schemas for analysis, managed, browser, Electron, application, and session tools"]
+ CONTRACTS["src/contracts/
Caller-visible schemas for analysis, reverse engineering, managed, browser, Electron, application, and session tools"]
ERRORS["src/contracts/errorSchemas.ts
Shared analysis error schemas
CLI diagnostics, generated error catalog"]
end
@@ -122,6 +126,14 @@ graph TD
INSPECTOR --> DOMAIN
APP --> DOTNET
DOTNET --> DOMAIN
+ COMPOSITION --> OBJDUMP
+ OBJDUMP --> DOMAIN
+ COMPOSITION --> RIZIN
+ RIZIN --> DOMAIN
+ CLI --> GDB
+ SERVER --> GDB
+ GDB --> PROCESS
+ GDB --> DOMAIN
HOPPER --> HOPPER_BRIDGE
GHIDRA --> GHIDRA_BRIDGE
HOPPER --> PROCESS
diff --git a/docs/readme/README_ar.md b/docs/readme/README_ar.md
index f162a732f..2c796533e 100644
--- a/docs/readme/README_ar.md
+++ b/docs/readme/README_ar.md
@@ -19,7 +19,7 @@
**[الموقع](https://rea.tools/ar/) · [الأدلة](https://rea.tools/ar/guides/) · [دراسات الحالة](https://rea.tools/ar/showcase/)**
-[البدء السريع](#البدء-السريع) · [كيف يعمل REA](#كيف-يعمل-rea) · [ما الذي يمكنك تحليله](#ما-الذي-يمكنك-تحليله) · [دراسات الحالة](#دراسات-الحالة) · [الأسئلة الشائعة](#الأسئلة-الشائعة) · [التوثيق](#التوثيق)
+[البدء السريع](#البدء-السريع) · [كيف يعمل REA](#كيف-يعمل-rea) · [ما الذي يمكنك تحليله](#ما-الذي-يمكنك-تحليله) · [دراسات الحالة](#دراسات-الحالة) · [الأسئلة الشائعة](#الأسئلة-الشائعة) · [أدوات GNU objdump وRizin](../reverse-engineering-tools.md) · [التوثيق](#التوثيق)
> [!IMPORTANT]
> المصادر الرسمية لـ REA: [morluto/rea](https://github.com/morluto/rea), [rea.tools](https://rea.tools/), [rea-agents](https://www.npmjs.com/package/rea-agents) (npm).
diff --git a/docs/readme/README_de.md b/docs/readme/README_de.md
index b8d3c82c6..c6f768824 100644
--- a/docs/readme/README_de.md
+++ b/docs/readme/README_de.md
@@ -19,7 +19,7 @@
**[Website](https://rea.tools/de/) · [Anleitungen](https://rea.tools/de/guides/) · [Fallstudien](https://rea.tools/de/showcase/)**
-[Schnellstart](#schnellstart) · [So funktioniert REA](#so-funktioniert-rea) · [Was du analysieren kannst](#was-du-analysieren-kannst) · [Fallstudien](#fallstudien) · [Häufige Fragen](#häufige-fragen) · [Dokumentation](#dokumentation)
+[Schnellstart](#schnellstart) · [So funktioniert REA](#so-funktioniert-rea) · [Was du analysieren kannst](#was-du-analysieren-kannst) · [Fallstudien](#fallstudien) · [Häufige Fragen](#häufige-fragen) · [GNU-objdump- und Rizin-Tools](../reverse-engineering-tools.md) · [Dokumentation](#dokumentation)
> [!IMPORTANT]
> Offizielle Quellen für REA: [morluto/rea](https://github.com/morluto/rea), [rea.tools](https://rea.tools/), [rea-agents](https://www.npmjs.com/package/rea-agents) (npm).
diff --git a/docs/readme/README_es.md b/docs/readme/README_es.md
index 94c1b3077..ba11c44f2 100644
--- a/docs/readme/README_es.md
+++ b/docs/readme/README_es.md
@@ -19,7 +19,7 @@
**[Sitio web](https://rea.tools/es/) · [Guías](https://rea.tools/es/guides/) · [Casos prácticos](https://rea.tools/es/showcase/)**
-[Inicio rápido](#inicio-rápido) · [Cómo funciona REA](#cómo-funciona-rea) · [Qué puedes analizar](#qué-puedes-analizar) · [Casos prácticos](#casos-prácticos) · [Preguntas frecuentes](#preguntas-frecuentes) · [Documentación](#documentación)
+[Inicio rápido](#inicio-rápido) · [Cómo funciona REA](#cómo-funciona-rea) · [Qué puedes analizar](#qué-puedes-analizar) · [Casos prácticos](#casos-prácticos) · [Preguntas frecuentes](#preguntas-frecuentes) · [Herramientas GNU objdump y Rizin](../reverse-engineering-tools.md) · [Documentación](#documentación)
> [!IMPORTANT]
> Fuentes oficiales de REA: [morluto/rea](https://github.com/morluto/rea), [rea.tools](https://rea.tools/), [rea-agents](https://www.npmjs.com/package/rea-agents) (npm).
diff --git a/docs/readme/README_fa.md b/docs/readme/README_fa.md
index 5304f470e..43aae1384 100644
--- a/docs/readme/README_fa.md
+++ b/docs/readme/README_fa.md
@@ -19,7 +19,7 @@
**[وبسایت](https://rea.tools/fa/) · [راهنماها](https://rea.tools/fa/guides/) · [نمونههای عملی](https://rea.tools/fa/showcase/)**
-[شروع سریع](#شروع-سریع) · [REA چگونه کار میکند؟](#rea-چگونه-کار-میکند) · [چه چیزهایی را میتوان تحلیل کرد؟](#چه-چیزهایی-را-میتوان-تحلیل-کرد) · [نمونههای عملی](#نمونههای-عملی) · [پرسشهای متداول](#پرسشهای-متداول) · [مستندات](#مستندات)
+[شروع سریع](#شروع-سریع) · [REA چگونه کار میکند؟](#rea-چگونه-کار-میکند) · [چه چیزهایی را میتوان تحلیل کرد؟](#چه-چیزهایی-را-میتوان-تحلیل-کرد) · [نمونههای عملی](#نمونههای-عملی) · [پرسشهای متداول](#پرسشهای-متداول) · [ابزارهای GNU objdump و Rizin](../reverse-engineering-tools.md) · [مستندات](#مستندات)
> [!IMPORTANT]
> منابع رسمی REA: [morluto/rea](https://github.com/morluto/rea), [rea.tools](https://rea.tools/), [rea-agents](https://www.npmjs.com/package/rea-agents) (npm).
diff --git a/docs/readme/README_fr.md b/docs/readme/README_fr.md
index 0fae5db84..0ff19b62d 100644
--- a/docs/readme/README_fr.md
+++ b/docs/readme/README_fr.md
@@ -19,7 +19,7 @@
**[Site web](https://rea.tools/fr/) · [Guides](https://rea.tools/fr/guides/) · [Études de cas](https://rea.tools/fr/showcase/)**
-[Démarrage rapide](#démarrage-rapide) · [Comment fonctionne REA](#comment-fonctionne-rea) · [Ce que vous pouvez analyser](#ce-que-vous-pouvez-analyser) · [Études de cas](#études-de-cas) · [Questions fréquentes](#questions-fréquentes) · [Documentation](#documentation)
+[Démarrage rapide](#démarrage-rapide) · [Comment fonctionne REA](#comment-fonctionne-rea) · [Ce que vous pouvez analyser](#ce-que-vous-pouvez-analyser) · [Études de cas](#études-de-cas) · [Questions fréquentes](#questions-fréquentes) · [Outils GNU objdump et Rizin](../reverse-engineering-tools.md) · [Documentation](#documentation)
> [!IMPORTANT]
> Sources officielles de REA : [morluto/rea](https://github.com/morluto/rea), [rea.tools](https://rea.tools/), [rea-agents](https://www.npmjs.com/package/rea-agents) (npm).
diff --git a/docs/readme/README_id.md b/docs/readme/README_id.md
index 196bb8517..f880983ca 100644
--- a/docs/readme/README_id.md
+++ b/docs/readme/README_id.md
@@ -19,7 +19,7 @@
**[Situs Web](https://rea.tools/id/) · [Panduan](https://rea.tools/id/guides/) · [Contoh Penggunaan](https://rea.tools/id/showcase/)**
-[Mulai Cepat](#mulai-cepat) · [Cara Kerja REA](#cara-kerja-rea) · [Hal yang Dapat Anda Analisis](#hal-yang-dapat-anda-analisis) · [Contoh Penggunaan](#contoh-penggunaan) · [FAQ](#faq) · [Dokumentasi](#dokumentasi)
+[Mulai Cepat](#mulai-cepat) · [Cara Kerja REA](#cara-kerja-rea) · [Hal yang Dapat Anda Analisis](#hal-yang-dapat-anda-analisis) · [Contoh Penggunaan](#contoh-penggunaan) · [FAQ](#faq) · [Alat GNU objdump dan Rizin](../reverse-engineering-tools.md) · [Dokumentasi](#dokumentasi)
> [!IMPORTANT]
> Sumber resmi REA: [morluto/rea](https://github.com/morluto/rea), [rea.tools](https://rea.tools/), [rea-agents](https://www.npmjs.com/package/rea-agents) (npm).
diff --git a/docs/readme/README_it.md b/docs/readme/README_it.md
index ad16f51c0..231ae2587 100644
--- a/docs/readme/README_it.md
+++ b/docs/readme/README_it.md
@@ -19,7 +19,7 @@
**[Sito web](https://rea.tools/it/) · [Guide](https://rea.tools/it/guides/) · [Showcase](https://rea.tools/it/showcase/)**
-[Avvio rapido](#avvio-rapido) · [Come funziona REA](#come-funziona-rea) · [Cosa puoi analizzare](#cosa-puoi-analizzare) · [Showcase](#showcase) · [FAQ](#faq) · [Documentazione](#documentazione)
+[Avvio rapido](#avvio-rapido) · [Come funziona REA](#come-funziona-rea) · [Cosa puoi analizzare](#cosa-puoi-analizzare) · [Showcase](#showcase) · [FAQ](#faq) · [Strumenti GNU objdump e Rizin](../reverse-engineering-tools.md) · [Documentazione](#documentazione)
> [!IMPORTANT]
> Fonti ufficiali di REA: [morluto/rea](https://github.com/morluto/rea), [rea.tools](https://rea.tools/), [rea-agents](https://www.npmjs.com/package/rea-agents) (npm).
diff --git a/docs/readme/README_ja.md b/docs/readme/README_ja.md
index cf0b1f755..3377c52bc 100644
--- a/docs/readme/README_ja.md
+++ b/docs/readme/README_ja.md
@@ -19,7 +19,7 @@
**[ウェブサイト](https://rea.tools/ja/) · [ガイド](https://rea.tools/ja/guides/) · [事例](https://rea.tools/ja/showcase/)**
-[クイックスタート](#クイックスタート) · [REA の仕組み](#rea-の仕組み) · [分析できる対象](#分析できる対象) · [事例](#事例) · [よくある質問](#よくある質問) · [ドキュメント](#ドキュメント)
+[クイックスタート](#クイックスタート) · [REA の仕組み](#rea-の仕組み) · [分析できる対象](#分析できる対象) · [事例](#事例) · [よくある質問](#よくある質問) · [GNU objdump・Rizin ツール](../reverse-engineering-tools.md) · [ドキュメント](#ドキュメント)
> [!IMPORTANT]
> REA の公式配布元: [morluto/rea](https://github.com/morluto/rea), [rea.tools](https://rea.tools/), [rea-agents](https://www.npmjs.com/package/rea-agents) (npm).
diff --git a/docs/readme/README_ko.md b/docs/readme/README_ko.md
index aebc6a705..6eb9ae9a7 100644
--- a/docs/readme/README_ko.md
+++ b/docs/readme/README_ko.md
@@ -19,7 +19,7 @@
**[웹사이트](https://rea.tools/ko/) · [가이드](https://rea.tools/ko/guides/) · [사례](https://rea.tools/ko/showcase/)**
-[빠른 시작](#빠른-시작) · [REA의 작동 방식](#rea의-작동-방식) · [분석할 수 있는 대상](#분석할-수-있는-대상) · [사례](#사례) · [자주 묻는 질문](#자주-묻는-질문) · [문서](#문서)
+[빠른 시작](#빠른-시작) · [REA의 작동 방식](#rea의-작동-방식) · [분석할 수 있는 대상](#분석할-수-있는-대상) · [사례](#사례) · [자주 묻는 질문](#자주-묻는-질문) · [GNU objdump 및 Rizin 도구](../reverse-engineering-tools.md) · [문서](#문서)
> [!IMPORTANT]
> REA 공식 배포처: [morluto/rea](https://github.com/morluto/rea), [rea.tools](https://rea.tools/), [rea-agents](https://www.npmjs.com/package/rea-agents) (npm).
diff --git a/docs/readme/README_pl.md b/docs/readme/README_pl.md
index c613600e0..96b46685d 100644
--- a/docs/readme/README_pl.md
+++ b/docs/readme/README_pl.md
@@ -19,7 +19,7 @@
**[Strona internetowa](https://rea.tools/pl/) · [Przewodniki](https://rea.tools/pl/guides/) · [Przykłady zastosowań](https://rea.tools/pl/showcase/)**
-[Szybki start](#szybki-start) · [Jak działa REA](#jak-działa-rea) · [Co możesz analizować](#co-możesz-analizować) · [Przykłady zastosowań](#przykłady-zastosowań) · [FAQ](#faq) · [Dokumentacja](#dokumentacja)
+[Szybki start](#szybki-start) · [Jak działa REA](#jak-działa-rea) · [Co możesz analizować](#co-możesz-analizować) · [Przykłady zastosowań](#przykłady-zastosowań) · [FAQ](#faq) · [Narzędzia GNU objdump i Rizin](../reverse-engineering-tools.md) · [Dokumentacja](#dokumentacja)
> [!IMPORTANT]
> Oficjalne źródła REA: [morluto/rea](https://github.com/morluto/rea), [rea.tools](https://rea.tools/), [rea-agents](https://www.npmjs.com/package/rea-agents) (npm).
diff --git a/docs/readme/README_pt-BR.md b/docs/readme/README_pt-BR.md
index 3bd728ac8..7988f0d77 100644
--- a/docs/readme/README_pt-BR.md
+++ b/docs/readme/README_pt-BR.md
@@ -19,7 +19,7 @@
**[Site](https://rea.tools/pt-BR/) · [Guias](https://rea.tools/pt-BR/guides/) · [Casos de uso](https://rea.tools/pt-BR/showcase/)**
-[Início rápido](#início-rápido) · [Como o REA funciona](#como-o-rea-funciona) · [O que você pode analisar](#o-que-você-pode-analisar) · [Casos de uso](#casos-de-uso) · [Perguntas frequentes](#perguntas-frequentes) · [Documentação](#documentação)
+[Início rápido](#início-rápido) · [Como o REA funciona](#como-o-rea-funciona) · [O que você pode analisar](#o-que-você-pode-analisar) · [Casos de uso](#casos-de-uso) · [Perguntas frequentes](#perguntas-frequentes) · [Ferramentas GNU objdump e Rizin](../reverse-engineering-tools.md) · [Documentação](#documentação)
> [!IMPORTANT]
> Fontes oficiais do REA: [morluto/rea](https://github.com/morluto/rea), [rea.tools](https://rea.tools/), [rea-agents](https://www.npmjs.com/package/rea-agents) (npm).
diff --git a/docs/readme/README_ru.md b/docs/readme/README_ru.md
index 8bb201e36..bf2cf30e6 100644
--- a/docs/readme/README_ru.md
+++ b/docs/readme/README_ru.md
@@ -19,7 +19,7 @@
**[Сайт](https://rea.tools/ru/) · [Руководства](https://rea.tools/ru/guides/) · [Примеры](https://rea.tools/ru/showcase/)**
-[Быстрый старт](#быстрый-старт) · [Как работает REA](#как-работает-rea) · [Что можно анализировать](#что-можно-анализировать) · [Примеры](#примеры) · [Частые вопросы](#частые-вопросы) · [Документация](#документация)
+[Быстрый старт](#быстрый-старт) · [Как работает REA](#как-работает-rea) · [Что можно анализировать](#что-можно-анализировать) · [Примеры](#примеры) · [Частые вопросы](#частые-вопросы) · [Инструменты GNU objdump и Rizin](../reverse-engineering-tools.md) · [Документация](#документация)
> [!IMPORTANT]
> Официальные источники REA: [morluto/rea](https://github.com/morluto/rea), [rea.tools](https://rea.tools/), [rea-agents](https://www.npmjs.com/package/rea-agents) (npm).
diff --git a/docs/readme/README_th.md b/docs/readme/README_th.md
index 892ee0209..5584157c3 100644
--- a/docs/readme/README_th.md
+++ b/docs/readme/README_th.md
@@ -19,7 +19,7 @@
**[เว็บไซต์](https://rea.tools/th/) · [คู่มือ](https://rea.tools/th/guides/) · [ตัวอย่างการใช้งาน](https://rea.tools/th/showcase/)**
-[เริ่มต้นอย่างรวดเร็ว](#เริ่มต้นอย่างรวดเร็ว) · [REA ทำงานอย่างไร](#rea-ทำงานอย่างไร) · [สิ่งที่วิเคราะห์ได้](#สิ่งที่วิเคราะห์ได้) · [ตัวอย่างการใช้งาน](#ตัวอย่างการใช้งาน) · [คำถามที่พบบ่อย](#คำถามที่พบบ่อย) · [เอกสาร](#เอกสาร)
+[เริ่มต้นอย่างรวดเร็ว](#เริ่มต้นอย่างรวดเร็ว) · [REA ทำงานอย่างไร](#rea-ทำงานอย่างไร) · [สิ่งที่วิเคราะห์ได้](#สิ่งที่วิเคราะห์ได้) · [ตัวอย่างการใช้งาน](#ตัวอย่างการใช้งาน) · [คำถามที่พบบ่อย](#คำถามที่พบบ่อย) · [เครื่องมือ GNU objdump และ Rizin](../reverse-engineering-tools.md) · [เอกสาร](#เอกสาร)
> [!IMPORTANT]
> แหล่งทางการของ REA: [morluto/rea](https://github.com/morluto/rea), [rea.tools](https://rea.tools/), [rea-agents](https://www.npmjs.com/package/rea-agents) (npm).
diff --git a/docs/readme/README_tr.md b/docs/readme/README_tr.md
index 0a695396c..ea09a48b8 100644
--- a/docs/readme/README_tr.md
+++ b/docs/readme/README_tr.md
@@ -19,7 +19,7 @@
**[Web sitesi](https://rea.tools/tr/) · [Rehberler](https://rea.tools/tr/guides/) · [Örnek çalışmalar](https://rea.tools/tr/showcase/)**
-[Hızlı başlangıç](#hızlı-başlangıç) · [REA nasıl çalışır](#rea-nasıl-çalışır) · [Neleri analiz edebilirsiniz](#neleri-analiz-edebilirsiniz) · [Örnek çalışmalar](#örnek-çalışmalar) · [Sık sorulan sorular](#sık-sorulan-sorular) · [Belgeler](#belgeler)
+[Hızlı başlangıç](#hızlı-başlangıç) · [REA nasıl çalışır](#rea-nasıl-çalışır) · [Neleri analiz edebilirsiniz](#neleri-analiz-edebilirsiniz) · [Örnek çalışmalar](#örnek-çalışmalar) · [Sık sorulan sorular](#sık-sorulan-sorular) · [GNU objdump ve Rizin araçları](../reverse-engineering-tools.md) · [Belgeler](#belgeler)
> [!IMPORTANT]
> REA’nın resmî kaynakları: [morluto/rea](https://github.com/morluto/rea), [rea.tools](https://rea.tools/), [rea-agents](https://www.npmjs.com/package/rea-agents) (npm).
diff --git a/docs/readme/README_uk.md b/docs/readme/README_uk.md
index 1078ddf9d..ceec0b63b 100644
--- a/docs/readme/README_uk.md
+++ b/docs/readme/README_uk.md
@@ -19,7 +19,7 @@
**[Сайт](https://rea.tools/uk/) · [Посібники](https://rea.tools/uk/guides/) · [Приклади](https://rea.tools/uk/showcase/)**
-[Швидкий старт](#швидкий-старт) · [Як працює REA](#як-працює-rea) · [Що можна аналізувати](#що-можна-аналізувати) · [Приклади](#приклади) · [Поширені запитання](#поширені-запитання) · [Документація](#документація)
+[Швидкий старт](#швидкий-старт) · [Як працює REA](#як-працює-rea) · [Що можна аналізувати](#що-можна-аналізувати) · [Приклади](#приклади) · [Поширені запитання](#поширені-запитання) · [Інструменти GNU objdump і Rizin](../reverse-engineering-tools.md) · [Документація](#документація)
> [!IMPORTANT]
> Офіційні джерела REA: [morluto/rea](https://github.com/morluto/rea), [rea.tools](https://rea.tools/), [rea-agents](https://www.npmjs.com/package/rea-agents) (npm).
diff --git a/docs/readme/README_vi.md b/docs/readme/README_vi.md
index d42c83ded..4f7aba8b6 100644
--- a/docs/readme/README_vi.md
+++ b/docs/readme/README_vi.md
@@ -19,7 +19,7 @@
**[Trang web](https://rea.tools/vi/) · [Hướng dẫn](https://rea.tools/vi/guides/) · [Các nghiên cứu điển hình](https://rea.tools/vi/showcase/)**
-[Bắt đầu nhanh](#bắt-đầu-nhanh) · [Cách REA hoạt động](#cách-rea-hoạt-động) · [Những gì bạn có thể phân tích](#những-gì-bạn-có-thể-phân-tích) · [Các nghiên cứu điển hình](#các-nghiên-cứu-điển-hình) · [Câu hỏi thường gặp](#câu-hỏi-thường-gặp) · [Tài liệu](#tài-liệu)
+[Bắt đầu nhanh](#bắt-đầu-nhanh) · [Cách REA hoạt động](#cách-rea-hoạt-động) · [Những gì bạn có thể phân tích](#những-gì-bạn-có-thể-phân-tích) · [Các nghiên cứu điển hình](#các-nghiên-cứu-điển-hình) · [Câu hỏi thường gặp](#câu-hỏi-thường-gặp) · [Công cụ GNU objdump và Rizin](../reverse-engineering-tools.md) · [Tài liệu](#tài-liệu)
> [!IMPORTANT]
> Các nguồn chính thức của REA: [morluto/rea](https://github.com/morluto/rea), [rea.tools](https://rea.tools/), [rea-agents](https://www.npmjs.com/package/rea-agents) (npm).
diff --git a/docs/readme/README_zh-TW.md b/docs/readme/README_zh-TW.md
index 489d7d6d3..95dedaccd 100644
--- a/docs/readme/README_zh-TW.md
+++ b/docs/readme/README_zh-TW.md
@@ -19,7 +19,7 @@
**[網站](https://rea.tools/zh-TW/) · [指南](https://rea.tools/zh-TW/guides/) · [案例](https://rea.tools/zh-TW/showcase/)**
-[快速開始](#快速開始) · [REA 的工作原理](#rea-的工作原理) · [可以分析什麼](#可以分析什麼) · [案例](#案例) · [常見問題](#常見問題) · [文件](#文件)
+[快速開始](#快速開始) · [REA 的工作原理](#rea-的工作原理) · [可以分析什麼](#可以分析什麼) · [案例](#案例) · [常見問題](#常見問題) · [GNU objdump 與 Rizin 工具](../reverse-engineering-tools.md) · [文件](#文件)
> [!IMPORTANT]
> REA 官方來源: [morluto/rea](https://github.com/morluto/rea), [rea.tools](https://rea.tools/), [rea-agents](https://www.npmjs.com/package/rea-agents) (npm).
diff --git a/docs/readme/README_zh.md b/docs/readme/README_zh.md
index c8122cade..64fc99193 100644
--- a/docs/readme/README_zh.md
+++ b/docs/readme/README_zh.md
@@ -19,7 +19,7 @@
**[网站](https://rea.tools/zh-CN/) · [指南](https://rea.tools/zh-CN/guides/) · [案例](https://rea.tools/zh-CN/showcase/)**
-[快速开始](#快速开始) · [REA 的工作原理](#rea-的工作原理) · [可以分析什么](#可以分析什么) · [案例](#案例) · [常见问题](#常见问题) · [文档](#文档)
+[快速开始](#快速开始) · [REA 的工作原理](#rea-的工作原理) · [可以分析什么](#可以分析什么) · [案例](#案例) · [常见问题](#常见问题) · [GNU objdump 和 Rizin 工具](../reverse-engineering-tools.md) · [文档](#文档)
> [!IMPORTANT]
> REA 官方来源: [morluto/rea](https://github.com/morluto/rea), [rea.tools](https://rea.tools/), [rea-agents](https://www.npmjs.com/package/rea-agents) (npm).
diff --git a/docs/reverse-engineering-tools.md b/docs/reverse-engineering-tools.md
new file mode 100644
index 000000000..124c92dc0
--- /dev/null
+++ b/docs/reverse-engineering-tools.md
@@ -0,0 +1,81 @@
+# Reverse-engineering tool integrations
+
+REA can call caller-supplied GNU objdump, GDB, and Rizin installations. REA does not install or update these tools. Static objdump/Rizin workflows use the same services from CLI and MCP. GDB's persistent MCP sessions and ephemeral CLI command are available only on Linux. Persistent debugger sessions exist only for the lifetime of the REA MCP server process.
+
+In a shared MCP server, unrestricted Rizin and Cutter commands, GDB console
+commands, Rizin debugger start/close, and GDB debugger close retire any active
+binary analysis session after the operation. This prevents a fresh analysis
+provider from being rebound automatically while command effects may still be
+writing. After the command and any background writers have stopped, call
+`open_binary` explicitly before continuing analysis. A successful command
+response alone does not prove that background writers have stopped. An active
+Ghidra analysis session must be closed explicitly before an external mutation
+can run.
+
+## GNU objdump
+
+`inspect_with_objdump` and `rea inspect-with-objdump` capture stdout and stderr as inline Evidence. Artifact inspection Evidence records SHA-256 digests before and after the provider call and whether those observed digests differ; this cannot prove the exact bytes consumed if the artifact changes during execution. Choose one artifact operation: `file_headers`, `section_headers`, `symbols`, `relocations`, `dwarf`, `disassemble`, or `disassemble_all`. `disassemble` maps to `-d`; `disassemble_all` maps to `-D` and has different symbol-boundary semantics. The pathless `inspect_with_objdump {"operation":"architectures"}` MCP call or `rea list-objdump-architectures` CLI command maps to `-i` and produces subjectless provider-capability Evidence describing BFD targets compiled into the installed binary, not a guarantee that a target is supported.
+
+For DWARF, REA does not follow local debug links by default. Set `follow_debug_links` to `true` to pass `-WK`; external debug-file identities are not bound to Evidence when following links is enabled. The CLI option is `--follow-debug-links true`. REA clears `DEBUGINFOD_URLS` in the objdump child environment to disable debuginfod network access on GNU binutils versions that do not support `-WE`. The complete raw output is authoritative; REA does not normalize it into a version-independent disassembly schema.
+
+```sh
+rea inspect-with-objdump ./program disassemble
+rea inspect-with-objdump ./program dwarf --follow-debug-links false
+```
+
+Captured stdout and stderr are retained up to a 16 MiB combined limit; `output_truncated` marks output beyond that limit.
+
+## GDB
+
+Persistent GDB sessions, including the MCP workflow and one-shot `rea debug-with-gdb` CLI, are available only on Linux. REA verifies a live inferior's output descriptors through `/proc` before detaching it; it does not claim equivalent descriptor verification on macOS. Windows is also unsupported because owned Job Object cleanup can terminate debugger inferiors. On supported Linux hosts, `start_gdb_session`, `gdb_console`, `gdb_session_status`, and `close_gdb_session` manage a persistent GDB/MI session in MCP. The one-shot CLI requires a caller-selected durable TTY: `rea debug-with-gdb --inferior-tty /dev/pts/7 'file ./program'`. The TTY must remain available after GDB exits so a detached inferior can continue writing. MCP starts GDB with `--nx --quiet --interpreter=mi3`, retries initialization with MI2 if MI3 initialization returns an error and cleanup succeeds, waits for its prompt, and disables auto-loading. Pass optional `inferior_tty` to select the terminal that future inferior runs use; REA configures it before the session becomes ready and checks the exact value with GDB/MI. `gdb_session_status` reports the selected path, whether REA still considers the configuration verified, and whether invalid UTF-8 has been observed in MI bytes (`mi_invalid_utf8`) or stderr (`stderr_invalid_utf8`). Commands that can change the inferior TTY or redirect a run revoke that verification permanently for the session, so close retains GDB ownership if a live inferior remains. Closing a session with one live inferior and a verified TTY automatically detaches the target, which can resume execution; MCP clients receive destructive-operation annotations for this behavior. The console remains unrestricted and can run shell commands, change target state, access files, and use network in the REA process user's security context. Raw MI records and console/target/log streams are returned with Evidence; command output is retained up to 16 MiB and explicitly marked when truncated.
+
+Before a normal close, REA checks for live GDB thread groups. If one remains and a durable `inferior_tty` is verified, REA asks GDB to detach it and confirms the detach before stopping the debugger. Without that sink, select and apply a disposition to each inferior through `gdb_console` (for example, detach or kill as appropriate), then retry close. REA never infers target ownership from a PID or path. When the MCP server shuts down, REA terminates only its directly owned GDB process after confirming no inferior or a sink-backed detach; otherwise it retains the process and its output streams for retry.
+
+```text
+start_gdb_session {"inferior_tty":"/dev/pts/7"}
+gdb_console {"session_id":"…","command":"file ./program"}
+gdb_console {"session_id":"…","command":"info files"}
+gdb_session_status {"session_id":"…"}
+close_gdb_session {"session_id":"…"}
+```
+
+## Rizin
+
+`start_rizin_debug_session` and `rea debug-with-rizin` require an explicit `inferior_tty` on Linux. Rizin launches the target with stdout and stderr routed to that caller-selected TTY and `dbg.exitkills=false`; the target can therefore continue running and writing to the TTY after REA closes the debugger. This is a visible target output effect, and the caller is responsible for selecting a suitable terminal that remains available. Before closing, REA serializes after the active command, parses the structured `olj` descriptor list, detaches only a unique `ptrace://` descriptor, confirms its absence, reads back `dbg.exitkills=false`, and verifies `/proc//fd/1` and `/proc//fd/2` still identify the selected TTY by device and inode. Any malformed or ambiguous readback keeps the Rizin session owned for retry. Persistent Rizin debugger sessions are unavailable outside Linux because this verification depends on `/proc`.
+
+Persistent command Evidence retains the caller-selected and resolved startup paths in `parameters.launch_provenance`. These paths describe launch provenance, not the current target after unrestricted commands. `rizin_debug_session_status.output_invalid_utf8` reports whether lossy frame decoding has been observed during the session. Command Evidence sets `output_invalid_utf8` only when loss was observed in frames collected for that command, including retained partial output; earlier startup or command frames do not mark later valid output as lossy.
+
+```sh
+rea debug-with-rizin --inferior-tty /dev/pts/7 ./program dr
+```
+
+`execute_rizin_command` and `rea execute-rizin-command` run one selected command against a local artifact. The one-shot profile uses `-N -q -c`, suppressing user settings and scripts while leaving plugins available. Commands and plugins run with the REA process user's security context and may access files, the network, or perform other actions. REA preserves raw output and does not claim that command-specific JSON has one universal schema.
+
+```sh
+rea execute-rizin-command ./program iI
+```
+
+Captured stdout and stderr are retained up to a 16 MiB combined limit; `output_truncated` marks output beyond that limit.
+
+## Cutter bridge
+
+Cutter commands use the local REA Python bridge and authenticated loopback messages.
+The client and plugin must support the current v2 handshake; older peers are
+rejected before dispatch because they cannot preserve cross-caller recovery proof.
+The client checks the final encoded request against the bridge's 16 MiB frame
+limit before dispatch; an oversized command does not retain a cleanup owner.
+Discovery admits at most 256 descriptor candidates and reads and probes at most
+eight concurrently. A larger directory reports `bridge_directory_capacity_exceeded`.
+
+Terminal receipts are recovered before another command, including from a new CLI
+invocation. A signed status response also confirms completion of older bridge
+connections after its UI dispatch, preserving recovery proof for a caller whose
+receipt another caller has acknowledged. Neither receipt recovery nor a status
+probe retries the original command. Failed acknowledgements remain pending and
+block new commands until they can be retried successfully.
+
+## Configuration and coverage
+
+The optional `REA_OBJDUMP_COMMAND`, `REA_GDB_COMMAND`, and `REA_RIZIN_COMMAND` values select executable names or paths; otherwise REA searches `PATH`. Values are passed directly to process spawning, not expanded as shell command lines. Provider versions and capabilities remain unknown until observed from the installed tool and verified in the corresponding real-provider lane.
+
+Provider capture limits are separate from the MCP response budget. A result that fits the provider limit may still exceed the default 10 MiB MCP response budget and return a transport `resource_constraint`. Configure `REA_MCP_MAX_RESPONSE_BYTES` to match a larger client receive buffer when that client supports one; otherwise use retained Evidence/export or complete CLI JSON output. See [MCP response limits](./mcp-contracts.md#tool-results).
diff --git a/docs/testing.md b/docs/testing.md
index 08fe54c38..b543fe89f 100644
--- a/docs/testing.md
+++ b/docs/testing.md
@@ -262,6 +262,24 @@ CI owns coverage; aggregate and domain/contract thresholds are maintained in
`vitest.config.ts`. They are glob-specific and never updated automatically.
Coverage does not replace named boundary or real-provider scenarios.
+The `reverse-tools` commands run the focused tests and report coverage and
+mutation results without an additional percentage requirement. Test failures,
+invalid mutation configuration, and runner failures still fail the check.
+Mutation targets cover reviewed production behavior, including signal-terminated
+commands, Cutter descriptor identity, debugger cleanup ownership, GDB command
+exit and cancellation, and Rizin startup, timeout Evidence, and process exit.
+A second exhaustive pass runs the focused Cutter and Rizin timeout semantics tests against every compilable
+selected mutant to avoid relying on per-test mutation coverage associations;
+Stryker reports compile-error mutants separately because those cannot execute
+tests. Both passes resolve named syntax owners, scoped declarations, guards,
+callbacks, and reviewed expressions from the current TypeScript source; no
+hand-maintained line ranges select mutations. Missing, ambiguous, or malformed
+source targets stop configuration before mutation generation. The selections
+retain their review scope rather than expanding to whole source files. The
+focused suite also exercises affected MCP routes and the Python Cutter bridge.
+Contract, integration, and real-provider lanes complement these focused tests;
+simulated providers do not establish real-provider behavior.
+
CI runs six native Vitest shards without retries. Each shard emits a blob
report; the merge job produces JUnit and JSON diagnostics, a module timing
report, plus aggregate coverage during full qualification. It retains available
@@ -346,6 +364,17 @@ that explicit lane, not the routine iteration requirement.
## Real-toolchain verification lanes
+Linux Rizin debugger ownership changes require a separate real-provider gate on
+Rizin 0.7.4. Run
+`npm run test:focused -- tests/boundary/providers/RizinInferiorTty.test.ts`.
+This test verifies the caller-selected TTY against the actual inferior's file
+descriptors, confirms `dbg.exitkills=false`, detaches through the `olj`/`o-`
+protocol, and checks that the target survives debugger close and writes to the
+selected TTY. The suite is included in the reverse-tools Vitest and mutation
+typecheck selections; its real-provider assertions skip when Rizin 0.7.4 is
+unavailable. Run the focused command above on a host with that provider for the
+explicit real-provider verification gate.
+
Each real-toolchain command must require only the host tools needed to prove
its stated claim. Use a host-native fixture for host/provider acceptance, and
place optional cross-target formats or platform-specific runners in separate
diff --git a/knip.json b/knip.json
index 72d093397..5ed82edf0 100644
--- a/knip.json
+++ b/knip.json
@@ -9,6 +9,7 @@
"src/application/RealHopperAssertions.ts",
"scripts/verify/fridaBindingsCompatibility.ts",
"scripts/verify/termux/browser.mjs",
+ "scripts/verify-real-cutter.mjs",
"scripts/verify-real-ghidra-noreturn.mjs",
"tests/processOwnershipRunner.ts"
],
@@ -17,7 +18,12 @@
"entry": ["docs/.vitepress/config.ts"]
},
"ignoreBinaries": ["mkfifo"],
- "ignoreDependencies": ["electron", "esbuild", "webpack"],
+ "ignoreDependencies": [
+ "@stryker-mutator/command-runner",
+ "electron",
+ "esbuild",
+ "webpack"
+ ],
"ignoreExportsUsedInFile": true,
"rules": {
"files": "error",
diff --git a/licenses/npm-dependencies.txt b/licenses/npm-dependencies.txt
index 05fc2f173..794d5287e 100644
--- a/licenses/npm-dependencies.txt
+++ b/licenses/npm-dependencies.txt
@@ -2,7 +2,7 @@ REA npm dependency inventory
Prepared from the root package-lock.json; refresh this inventory when locked dependencies change.
License identifiers are upstream lockfile declarations, not a legal assessment.
Runtime includes transitive and optional platform packages; development includes build, test and website tooling.
-Locked package entries: 587; runtime: 175.
+Locked package entries: 719; runtime: 175.
Full runtime copyright/license/NOTICE texts: npm-runtime-notices.txt.
@algolia/abtesting@1.25.0 | development | MIT
@@ -95,21 +95,236 @@ Full runtime copyright/license/NOTICE texts: npm-runtime-notices.txt.
Package: https://www.npmjs.com/package/@algolia/requester-node-http/v/5.59.0
Archive: https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.59.0.tgz
Integrity: sha512-YYHLEs5rC6oRTFwT7bJaKBR9NSFzikeVHGAT1ffATmUQzR8XqyhHXjoieAUoR7fBU6I5cfy1FVeh5GoRgriuzA==
-@babel/helper-string-parser@8.0.0 | runtime | MIT
+@babel/code-frame@8.0.6 | development | MIT
+ Lockfile path: node_modules/@babel/code-frame
+ Package: https://www.npmjs.com/package/@babel/code-frame/v/8.0.6
+ Archive: https://registry.npmjs.org/@babel/code-frame/-/code-frame-8.0.6.tgz
+ Integrity: sha512-uMHhvmDmwFiLuiozaoRYTZHqMFuu3fem/X9JY78lohAuLoh8LSGGC8JqUzo2cVuBsBxkjMZNk56CE7ock8Xxkw==
+@babel/compat-data@8.0.5 | development | MIT
+ Lockfile path: node_modules/@babel/compat-data
+ Package: https://www.npmjs.com/package/@babel/compat-data/v/8.0.5
+ Archive: https://registry.npmjs.org/@babel/compat-data/-/compat-data-8.0.5.tgz
+ Integrity: sha512-YLsYoQMvL8l8WrGpN3Zj7O1wK5LEBN+cQtux7BcuHyxIXve724XG+zuJ1n3U1cUweRtTzQOA4IHbuQw3N34SZw==
+@babel/core@8.0.7 | development | MIT
+ Lockfile path: node_modules/@babel/core
+ Package: https://www.npmjs.com/package/@babel/core/v/8.0.7
+ Archive: https://registry.npmjs.org/@babel/core/-/core-8.0.7.tgz
+ Integrity: sha512-ZIsZWBG6Y1oa+8fkQVp0xeCdjCHBkXFCCIWrmg9SvejVvfcVQgKKRmGrBDWfM7rilhvnRoEuaqa5dkK7KB3pHA==
+@babel/parser@8.0.7 | development | MIT
+ Lockfile path: node_modules/@babel/core/node_modules/@babel/parser
+ Package: https://www.npmjs.com/package/@babel/parser/v/8.0.7
+ Archive: https://registry.npmjs.org/@babel/parser/-/parser-8.0.7.tgz
+ Integrity: sha512-UkDwEbPO1px2JQSrdh4qIgaH1JgG7GO/pFeJkpxhDxIETkT/QvGpnigXIw75ViHYPtIgKITmVUJqLjiO+JuWjA==
+@babel/types@8.0.6 | development | MIT
+ Lockfile path: node_modules/@babel/core/node_modules/@babel/types
+ Package: https://www.npmjs.com/package/@babel/types/v/8.0.6
+ Archive: https://registry.npmjs.org/@babel/types/-/types-8.0.6.tgz
+ Integrity: sha512-c+xgSWboV2pdwXP67BUWDVRHi2BO5Q3KwxyFVs7taVTY+fsK47/L51ZXQoV9rozHJDNHvJ4v4WEsI/IlxV2l2A==
+obug@3.0.0 | development | MIT
+ Lockfile path: node_modules/@babel/core/node_modules/obug
+ Package: https://www.npmjs.com/package/obug/v/3.0.0
+ Archive: https://registry.npmjs.org/obug/-/obug-3.0.0.tgz
+ Integrity: sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==
+@babel/generator@8.0.6 | development | MIT
+ Lockfile path: node_modules/@babel/generator
+ Package: https://www.npmjs.com/package/@babel/generator/v/8.0.6
+ Archive: https://registry.npmjs.org/@babel/generator/-/generator-8.0.6.tgz
+ Integrity: sha512-/pcanjdRsonCexCuxgGD8fKnBLLzk2i9uM4kmcmGyyev2R5YDs+rafMYZ89RvaGY1mW8T7C5au0TyYaEeHBGng==
+@babel/parser@8.0.7 | development | MIT
+ Lockfile path: node_modules/@babel/generator/node_modules/@babel/parser
+ Package: https://www.npmjs.com/package/@babel/parser/v/8.0.7
+ Archive: https://registry.npmjs.org/@babel/parser/-/parser-8.0.7.tgz
+ Integrity: sha512-UkDwEbPO1px2JQSrdh4qIgaH1JgG7GO/pFeJkpxhDxIETkT/QvGpnigXIw75ViHYPtIgKITmVUJqLjiO+JuWjA==
+@babel/types@8.0.6 | development | MIT
+ Lockfile path: node_modules/@babel/generator/node_modules/@babel/types
+ Package: https://www.npmjs.com/package/@babel/types/v/8.0.6
+ Archive: https://registry.npmjs.org/@babel/types/-/types-8.0.6.tgz
+ Integrity: sha512-c+xgSWboV2pdwXP67BUWDVRHi2BO5Q3KwxyFVs7taVTY+fsK47/L51ZXQoV9rozHJDNHvJ4v4WEsI/IlxV2l2A==
+@babel/helper-annotate-as-pure@8.0.0 | development | MIT
+ Lockfile path: node_modules/@babel/helper-annotate-as-pure
+ Package: https://www.npmjs.com/package/@babel/helper-annotate-as-pure/v/8.0.0
+ Archive: https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-8.0.0.tgz
+ Integrity: sha512-NSpMkMsvvZqzThJ0p1B02cbtA2ObEyfBvq950bmNkyxsxvcxwhvvCB036rKhlEnuBBo30bOrk13u3FzlKSoRrw==
+@babel/helper-compilation-targets@8.0.7 | development | MIT
+ Lockfile path: node_modules/@babel/helper-compilation-targets
+ Package: https://www.npmjs.com/package/@babel/helper-compilation-targets/v/8.0.7
+ Archive: https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-8.0.7.tgz
+ Integrity: sha512-ilAZCdOOlOVA7dQbaFNw2f/59p6GW33NxTopc3DvEE3efp3ZXTrGX/yZScaKw8/vd9bq31Ag0EcXiLw32bAgJg==
+@babel/helper-create-class-features-plugin@8.0.7 | development | MIT
+ Lockfile path: node_modules/@babel/helper-create-class-features-plugin
+ Package: https://www.npmjs.com/package/@babel/helper-create-class-features-plugin/v/8.0.7
+ Archive: https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-8.0.7.tgz
+ Integrity: sha512-7ug3KrOhNVPPxFFLTel0/pMzimKzU/vTlNJKVL/dKguCpmNJZjjibyFbWg+Qs8QHmiIZPNuZ2UNnDGdHWGEpOg==
+@babel/helper-globals@8.0.6 | development | MIT
+ Lockfile path: node_modules/@babel/helper-globals
+ Package: https://www.npmjs.com/package/@babel/helper-globals/v/8.0.6
+ Archive: https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-8.0.6.tgz
+ Integrity: sha512-OdnA/N52LhzFpeUuVz5YGuSWjc7TPHiFmVYZbPlSgExocO2GwbxVpSni9MVs5wwW+wZ4kPuwBEBN9pIPDYz1mA==
+@babel/helper-member-expression-to-functions@8.0.5 | development | MIT
+ Lockfile path: node_modules/@babel/helper-member-expression-to-functions
+ Package: https://www.npmjs.com/package/@babel/helper-member-expression-to-functions/v/8.0.5
+ Archive: https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-8.0.5.tgz
+ Integrity: sha512-GLe05QD98BkNFTkjaqeqF9QSRoHLKrrB4tpoplyQuuPrQJ72rtmkM4wvqJLX/sjZPqkbK5MUYSsuUTqdJoOVjA==
+@babel/types@8.0.6 | development | MIT
+ Lockfile path: node_modules/@babel/helper-member-expression-to-functions/node_modules/@babel/types
+ Package: https://www.npmjs.com/package/@babel/types/v/8.0.6
+ Archive: https://registry.npmjs.org/@babel/types/-/types-8.0.6.tgz
+ Integrity: sha512-c+xgSWboV2pdwXP67BUWDVRHi2BO5Q3KwxyFVs7taVTY+fsK47/L51ZXQoV9rozHJDNHvJ4v4WEsI/IlxV2l2A==
+@babel/helper-module-imports@8.0.0 | development | MIT
+ Lockfile path: node_modules/@babel/helper-module-imports
+ Package: https://www.npmjs.com/package/@babel/helper-module-imports/v/8.0.0
+ Archive: https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-8.0.0.tgz
+ Integrity: sha512-NZ7mSS93o4ndX4KrbD7W8Sf3QT8Qe24PrnFyUcuOPDzK6faqDFKjY9RG7he7+I7FdiQ4llpnosFqzrXa+Vy3Ew==
+@babel/helper-module-transforms@8.0.6 | development | MIT
+ Lockfile path: node_modules/@babel/helper-module-transforms
+ Package: https://www.npmjs.com/package/@babel/helper-module-transforms/v/8.0.6
+ Archive: https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-8.0.6.tgz
+ Integrity: sha512-YUi+3X0cb0v97xKhuqkLdrwQtP6XXv1eVbhLRCFyMwG481UskbMQk4mjH7K/Jg3oJ1KLskvFwwq4kF/QnABkWQ==
+@babel/helper-optimise-call-expression@8.0.0 | development | MIT
+ Lockfile path: node_modules/@babel/helper-optimise-call-expression
+ Package: https://www.npmjs.com/package/@babel/helper-optimise-call-expression/v/8.0.0
+ Archive: https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-8.0.0.tgz
+ Integrity: sha512-3W6satvtPuCUkUx63S2jMoW9EQNYkADgs1HTfufmL7gCmAulHMKupA/12WNz4A0GMMFn/YnWWwqOT9IZrJHQjg==
+@babel/helper-plugin-utils@8.0.1 | development | MIT
+ Lockfile path: node_modules/@babel/helper-plugin-utils
+ Package: https://www.npmjs.com/package/@babel/helper-plugin-utils/v/8.0.1
+ Archive: https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-8.0.1.tgz
+ Integrity: sha512-3PKFgjTyPlhFhorfP+SjKQxLViIL++zWjFOO4hGriYU+Bsm983DxEM1JmDRJVWXV0O9npu+xXRqz7Pbd3mh70g==
+@babel/helper-replace-supers@8.0.1 | development | MIT
+ Lockfile path: node_modules/@babel/helper-replace-supers
+ Package: https://www.npmjs.com/package/@babel/helper-replace-supers/v/8.0.1
+ Archive: https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-8.0.1.tgz
+ Integrity: sha512-B1SZADIcy3tmH8CmWvj4SHi/oAPom4UL3uknTc2QRNsPVLFk/sPnZvQL/8kj7Y5omvjMqie0vklvs6XM4OLW5Q==
+@babel/helper-skip-transparent-expression-wrappers@8.0.0 | development | MIT
+ Lockfile path: node_modules/@babel/helper-skip-transparent-expression-wrappers
+ Package: https://www.npmjs.com/package/@babel/helper-skip-transparent-expression-wrappers/v/8.0.0
+ Archive: https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-8.0.0.tgz
+ Integrity: sha512-xmCA9kP3IhySsqhzwIdWGlDN/1A4cCKNBO/uwZx/3YzmDoMePwno2Q5/Bq0q+tYaKbeF940YiKV/kaW8Mzvpjw==
+@babel/helper-string-parser@8.0.6 | runtime | MIT
Lockfile path: node_modules/@babel/helper-string-parser
- Package: https://www.npmjs.com/package/@babel/helper-string-parser/v/8.0.0
- Archive: https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz
- Integrity: sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==
-@babel/helper-validator-identifier@8.0.4 | runtime | MIT
+ Package: https://www.npmjs.com/package/@babel/helper-string-parser/v/8.0.6
+ Archive: https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.6.tgz
+ Integrity: sha512-+6HYEBnsDRZHJtUTAEfMQd0zMDqdFHS7C+5vAsEZqBUr3vEMWOWlHJ0Jz1gS8jJIlCvLsuA6ivmeoqzH936YcA==
+@babel/helper-validator-identifier@8.0.6 | runtime | MIT
Lockfile path: node_modules/@babel/helper-validator-identifier
- Package: https://www.npmjs.com/package/@babel/helper-validator-identifier/v/8.0.4
- Archive: https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz
- Integrity: sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==
+ Package: https://www.npmjs.com/package/@babel/helper-validator-identifier/v/8.0.6
+ Archive: https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.6.tgz
+ Integrity: sha512-HYx0YmuchRxRSpnjvcNffnPS8DCauKNmnumIuHG3jnQAVewRA7kfJGwF13ayEpSZs7s4R/oBH4eppuXcAQd5JQ==
+@babel/helper-validator-option@8.0.0 | development | MIT
+ Lockfile path: node_modules/@babel/helper-validator-option
+ Package: https://www.npmjs.com/package/@babel/helper-validator-option/v/8.0.0
+ Archive: https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-8.0.0.tgz
+ Integrity: sha512-U4Dybxh4WESWHt5XhBeExi4DrY0/DNK1aHpQbsrQXCUbFHuMweT0TpLEWKvaraV2Y6fS+ZXunsZ8zIuZIgvF2Q==
+@babel/helpers@8.0.7 | development | MIT
+ Lockfile path: node_modules/@babel/helpers
+ Package: https://www.npmjs.com/package/@babel/helpers/v/8.0.7
+ Archive: https://registry.npmjs.org/@babel/helpers/-/helpers-8.0.7.tgz
+ Integrity: sha512-5dMj5kFwS2AY7Dt4PZp4px3c5ThMbbEa7tZfuHTncCGSFYq8uoA1ufDE1IS/lYpQedE+3+GQ3cPZUiplIu195w==
+@babel/types@8.0.6 | development | MIT
+ Lockfile path: node_modules/@babel/helpers/node_modules/@babel/types
+ Package: https://www.npmjs.com/package/@babel/types/v/8.0.6
+ Archive: https://registry.npmjs.org/@babel/types/-/types-8.0.6.tgz
+ Integrity: sha512-c+xgSWboV2pdwXP67BUWDVRHi2BO5Q3KwxyFVs7taVTY+fsK47/L51ZXQoV9rozHJDNHvJ4v4WEsI/IlxV2l2A==
@babel/parser@8.0.4 | runtime | MIT
Lockfile path: node_modules/@babel/parser
Package: https://www.npmjs.com/package/@babel/parser/v/8.0.4
Archive: https://registry.npmjs.org/@babel/parser/-/parser-8.0.4.tgz
Integrity: sha512-srpptsAkEbbNIC/q8nT7o+m6CQe8CJUTV/t7MYc9NnWlgYVtHOb7JH6SorxMhN0kuRJjVqXbKClG6xSbPtzz+g==
+@babel/plugin-proposal-decorators@8.0.2 | development | MIT
+ Lockfile path: node_modules/@babel/plugin-proposal-decorators
+ Package: https://www.npmjs.com/package/@babel/plugin-proposal-decorators/v/8.0.2
+ Archive: https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-8.0.2.tgz
+ Integrity: sha512-+C6O6KKXU7BBq1GNaIkFJxrALUVGRcr+WeWm4OcuRl3h+l/CmNfcTLMrT2Lm3uvGBimBH/8pEBRrXJFLoO67Gg==
+@babel/plugin-syntax-decorators@8.0.1 | development | MIT
+ Lockfile path: node_modules/@babel/plugin-syntax-decorators
+ Package: https://www.npmjs.com/package/@babel/plugin-syntax-decorators/v/8.0.1
+ Archive: https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-8.0.1.tgz
+ Integrity: sha512-NI+0S/6MvR6GlcQFwjDZ+WIc2qvG6TXN534lYs9llNldwW4b7Dh6KTtk030FA0xWdYGs4t1lWo+OEWN8wGB+Nw==
+@babel/plugin-syntax-jsx@8.0.1 | development | MIT
+ Lockfile path: node_modules/@babel/plugin-syntax-jsx
+ Package: https://www.npmjs.com/package/@babel/plugin-syntax-jsx/v/8.0.1
+ Archive: https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-8.0.1.tgz
+ Integrity: sha512-n0jtCOxEovhU7METqSQjcZO9pX53nu9uNIjMS+hEt+Nt9jA7oOZoBIgbCxhhASmF6T6rPDGge5UAvh6Z4eFz/g==
+@babel/plugin-syntax-typescript@8.0.3 | development | MIT
+ Lockfile path: node_modules/@babel/plugin-syntax-typescript
+ Package: https://www.npmjs.com/package/@babel/plugin-syntax-typescript/v/8.0.3
+ Archive: https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-8.0.3.tgz
+ Integrity: sha512-jmTPwps7oSQSZaV1SxkQ3C12UWyufGysGc5OzDpZzvPAIX4mO7dJT3hoqkWVrSImvkcMiknir1iLN1SNV/CZzg==
+@babel/plugin-transform-destructuring@8.0.5 | development | MIT
+ Lockfile path: node_modules/@babel/plugin-transform-destructuring
+ Package: https://www.npmjs.com/package/@babel/plugin-transform-destructuring/v/8.0.5
+ Archive: https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-8.0.5.tgz
+ Integrity: sha512-evjxc5fGvpXG2WMGSalpX8IJZVumLIDgk5r5eRhvXRDqFX/GPWq1BeRfJalPCcwJfrR5A/wvegUhx9W5Nr7/mg==
+@babel/plugin-transform-explicit-resource-management@8.0.5 | development | MIT
+ Lockfile path: node_modules/@babel/plugin-transform-explicit-resource-management
+ Package: https://www.npmjs.com/package/@babel/plugin-transform-explicit-resource-management/v/8.0.5
+ Archive: https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-8.0.5.tgz
+ Integrity: sha512-Vr20jk/ZxRGu30o0jYvcKl2Pf0tiyr8H9q2ZGxYD6yjOpv9DdP6//I85q0cSOdADZhsu/ZZoApjGVCfl4xnVJw==
+@babel/plugin-transform-modules-commonjs@8.0.1 | development | MIT
+ Lockfile path: node_modules/@babel/plugin-transform-modules-commonjs
+ Package: https://www.npmjs.com/package/@babel/plugin-transform-modules-commonjs/v/8.0.1
+ Archive: https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-8.0.1.tgz
+ Integrity: sha512-PMuzulWrrzFNmY3lXSk/tV9NRb7y0eZZLJY4UEo2TKszroxvUZHAPPi+T9FDyrQhod+TQA+t+8/QYaaMpiEuhA==
+@babel/plugin-transform-react-display-name@8.0.1 | development | MIT
+ Lockfile path: node_modules/@babel/plugin-transform-react-display-name
+ Package: https://www.npmjs.com/package/@babel/plugin-transform-react-display-name/v/8.0.1
+ Archive: https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-8.0.1.tgz
+ Integrity: sha512-soLishXlkyu6jcICPyO3HEP7A3GCzKEnn7XfvYrImuWEOwFAz93qShmWSYPf5ww0ZkO4By0zsN2bVIDF54fSdA==
+@babel/plugin-transform-react-jsx@8.0.1 | development | MIT
+ Lockfile path: node_modules/@babel/plugin-transform-react-jsx
+ Package: https://www.npmjs.com/package/@babel/plugin-transform-react-jsx/v/8.0.1
+ Archive: https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-8.0.1.tgz
+ Integrity: sha512-NgkoF7Uq+30TmOPDdNUimT0Nta02uVjqJRFNlVWKrbOCu/CkzfHa4aMnIs0lMpkMmZmWA1e42Va+F04i/pY1zw==
+@babel/plugin-transform-react-jsx-development@8.0.1 | development | MIT
+ Lockfile path: node_modules/@babel/plugin-transform-react-jsx-development
+ Package: https://www.npmjs.com/package/@babel/plugin-transform-react-jsx-development/v/8.0.1
+ Archive: https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-8.0.1.tgz
+ Integrity: sha512-Hb+HUZpV9KFHjm+F+P3aLDMi8QXU9l3ROCQv20z18Me2sGyW5nNNR5YTevNlgHvCpFek3BnAwhDGq/BRndXViw==
+@babel/plugin-transform-react-pure-annotations@8.0.1 | development | MIT
+ Lockfile path: node_modules/@babel/plugin-transform-react-pure-annotations
+ Package: https://www.npmjs.com/package/@babel/plugin-transform-react-pure-annotations/v/8.0.1
+ Archive: https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-8.0.1.tgz
+ Integrity: sha512-7/8UwU8hoPBurXa9tUiTTC8aACTRy5tCqLUtqikHp2eGiWoEB57AduOdbQ71OOMTEvawKrGhv3WfzkDpI+/oSg==
+@babel/plugin-transform-typescript@8.0.6 | development | MIT
+ Lockfile path: node_modules/@babel/plugin-transform-typescript
+ Package: https://www.npmjs.com/package/@babel/plugin-transform-typescript/v/8.0.6
+ Archive: https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-8.0.6.tgz
+ Integrity: sha512-WF3yRlQtClzScvQbWct00ix63+w3DPiyU0WmCBU4YqK539n3VvJFElElpevoD8wfu6953CbiNoUZBi2B7/5jXw==
+@babel/preset-react@8.0.1 | development | MIT
+ Lockfile path: node_modules/@babel/preset-react
+ Package: https://www.npmjs.com/package/@babel/preset-react/v/8.0.1
+ Archive: https://registry.npmjs.org/@babel/preset-react/-/preset-react-8.0.1.tgz
+ Integrity: sha512-jrFuPp/pTddFZbtmWhdLNAYc6UMcpboeUPnw0BBrm4nOmcAko/1TRcFi1PzWCeOFRU+VaSiKmat87W1HvR7mIg==
+@babel/preset-typescript@8.0.7 | development | MIT
+ Lockfile path: node_modules/@babel/preset-typescript
+ Package: https://www.npmjs.com/package/@babel/preset-typescript/v/8.0.7
+ Archive: https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-8.0.7.tgz
+ Integrity: sha512-sUFLFphAG6YNjCnJpSKpyoNxkfyeM2KNuUjJJN0R6dZusMcK2V3Fg11MVHPFvb37AZeDNocc+xM748vdh9z82Q==
+@babel/template@8.0.0 | development | MIT
+ Lockfile path: node_modules/@babel/template
+ Package: https://www.npmjs.com/package/@babel/template/v/8.0.0
+ Archive: https://registry.npmjs.org/@babel/template/-/template-8.0.0.tgz
+ Integrity: sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ==
+@babel/traverse@8.0.7 | development | MIT
+ Lockfile path: node_modules/@babel/traverse
+ Package: https://www.npmjs.com/package/@babel/traverse/v/8.0.7
+ Archive: https://registry.npmjs.org/@babel/traverse/-/traverse-8.0.7.tgz
+ Integrity: sha512-bVV0brinsTWgc6ptSIoQo3GbITtl0VZAqEUJbHHcyfDPEu+q/fEvlYEa+6H85HoK8IdksKBFtKJbJFZ6Kff+zg==
+@babel/parser@8.0.7 | development | MIT
+ Lockfile path: node_modules/@babel/traverse/node_modules/@babel/parser
+ Package: https://www.npmjs.com/package/@babel/parser/v/8.0.7
+ Archive: https://registry.npmjs.org/@babel/parser/-/parser-8.0.7.tgz
+ Integrity: sha512-UkDwEbPO1px2JQSrdh4qIgaH1JgG7GO/pFeJkpxhDxIETkT/QvGpnigXIw75ViHYPtIgKITmVUJqLjiO+JuWjA==
+@babel/types@8.0.6 | development | MIT
+ Lockfile path: node_modules/@babel/traverse/node_modules/@babel/types
+ Package: https://www.npmjs.com/package/@babel/types/v/8.0.6
+ Archive: https://registry.npmjs.org/@babel/types/-/types-8.0.6.tgz
+ Integrity: sha512-c+xgSWboV2pdwXP67BUWDVRHi2BO5Q3KwxyFVs7taVTY+fsK47/L51ZXQoV9rozHJDNHvJ4v4WEsI/IlxV2l2A==
+obug@3.0.0 | development | MIT
+ Lockfile path: node_modules/@babel/traverse/node_modules/obug
+ Package: https://www.npmjs.com/package/obug/v/3.0.0
+ Archive: https://registry.npmjs.org/obug/-/obug-3.0.0.tgz
+ Integrity: sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==
@babel/types@8.0.4 | runtime | MIT
Lockfile path: node_modules/@babel/types
Package: https://www.npmjs.com/package/@babel/types/v/8.0.4
@@ -190,6 +405,91 @@ commander@13.1.0 | runtime | MIT
Package: https://www.npmjs.com/package/@iconify/types/v/2.0.0
Archive: https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz
Integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==
+@inquirer/ansi@2.0.9 | development | MIT
+ Lockfile path: node_modules/@inquirer/ansi
+ Package: https://www.npmjs.com/package/@inquirer/ansi/v/2.0.9
+ Archive: https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.9.tgz
+ Integrity: sha512-0Ywt3S8tqM6TWUcsRlBFQ1x+X3/o8kktX0ZiKAnByYb9GFDknnOhse1PVtfiD2BXPI8ar+BWx9PV9R0lQIcygw==
+@inquirer/checkbox@5.2.6 | development | MIT
+ Lockfile path: node_modules/@inquirer/checkbox
+ Package: https://www.npmjs.com/package/@inquirer/checkbox/v/5.2.6
+ Archive: https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-5.2.6.tgz
+ Integrity: sha512-XkOardciF3tow2NUlY6/Qjk1QqYa+6WpmVLTpxXl97Uv4erczDEZ+xg1j6LqBf0ekcRSUiICQA4nQXHHkVKQ9w==
+@inquirer/confirm@6.3.3 | development | MIT
+ Lockfile path: node_modules/@inquirer/confirm
+ Package: https://www.npmjs.com/package/@inquirer/confirm/v/6.3.3
+ Archive: https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.3.3.tgz
+ Integrity: sha512-ImOBgpl/sJTFp8naeSkNu4S0Sf8F8pxv3/CUx+LoBAdQvCVqFz6rSB2A/oZYsJge3+yphbEDxEkfaRNYq1OU0A==
+@inquirer/core@12.0.4 | development | MIT
+ Lockfile path: node_modules/@inquirer/core
+ Package: https://www.npmjs.com/package/@inquirer/core/v/12.0.4
+ Archive: https://registry.npmjs.org/@inquirer/core/-/core-12.0.4.tgz
+ Integrity: sha512-M5fB67y7jPObI45vhD82RskWwCSm0scOvenV/I6K8tIi2gfr9DXqQqJ+iw+Wi6SgapikdOiJibYzz1gW47fsDg==
+@inquirer/editor@5.3.4 | development | MIT
+ Lockfile path: node_modules/@inquirer/editor
+ Package: https://www.npmjs.com/package/@inquirer/editor/v/5.3.4
+ Archive: https://registry.npmjs.org/@inquirer/editor/-/editor-5.3.4.tgz
+ Integrity: sha512-imzxQDfw26NpcJAubMj/dE5RjU9Mp80IcT+FzsD6YoxXmL8L1QYnAFdCNv7ccGWh//73+0YP+1kfhwnExdl2VQ==
+@inquirer/expand@5.1.6 | development | MIT
+ Lockfile path: node_modules/@inquirer/expand
+ Package: https://www.npmjs.com/package/@inquirer/expand/v/5.1.6
+ Archive: https://registry.npmjs.org/@inquirer/expand/-/expand-5.1.6.tgz
+ Integrity: sha512-JfiLKi7qGuHmcQO/DqR10UFEp054Y1FXZaszP5ZEfHhgHeuFoT5nNFQIXFhed/5/p07SbsV2tIJFwG/dg5+NmQ==
+@inquirer/external-editor@3.0.6 | development | MIT
+ Lockfile path: node_modules/@inquirer/external-editor
+ Package: https://www.npmjs.com/package/@inquirer/external-editor/v/3.0.6
+ Archive: https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-3.0.6.tgz
+ Integrity: sha512-rWmw6WG8JZ7LMn/srXEylYGhL6EXXJeROE7/4x4GREh6NgzqJBDbIgpNGsYoLPfobeb4VZtEYzPGnT5EFNYhiQ==
+@inquirer/figures@2.0.9 | development | MIT
+ Lockfile path: node_modules/@inquirer/figures
+ Package: https://www.npmjs.com/package/@inquirer/figures/v/2.0.9
+ Archive: https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.9.tgz
+ Integrity: sha512-EAWgUTGQ/Umgga51dE3B2PUHbufuXarDfg86uVgoSgNHNNQnyFKcOrQLWVqYMghuSyHh8+2HUH0Js9cTC1WAdg==
+@inquirer/input@5.1.7 | development | MIT
+ Lockfile path: node_modules/@inquirer/input
+ Package: https://www.npmjs.com/package/@inquirer/input/v/5.1.7
+ Archive: https://registry.npmjs.org/@inquirer/input/-/input-5.1.7.tgz
+ Integrity: sha512-Icrs9tuMBCxGI+lFYNO71VFOnqGO8IIrLIGqnTGh07X8jZ2la9QLAYxXcikHZQjjVhIZUtU2xyH89DVN/pPhVg==
+@inquirer/number@4.2.4 | development | MIT
+ Lockfile path: node_modules/@inquirer/number
+ Package: https://www.npmjs.com/package/@inquirer/number/v/4.2.4
+ Archive: https://registry.npmjs.org/@inquirer/number/-/number-4.2.4.tgz
+ Integrity: sha512-C6g80U4U2+w2Vxa1CB0MN3a2WnQIfzLSUG0lduO3Au9GsirJsKATZkOwZPK+vZ1BlAlz+/Z5ParfQCS9E0svmg==
+@inquirer/password@5.2.3 | development | MIT
+ Lockfile path: node_modules/@inquirer/password
+ Package: https://www.npmjs.com/package/@inquirer/password/v/5.2.3
+ Archive: https://registry.npmjs.org/@inquirer/password/-/password-5.2.3.tgz
+ Integrity: sha512-1Uo2qfI2K3wXbfunMpvwAzoZPIUf5iEETwDGqRVbswoorXGqYQJ/bfIJFeLj+pxjq+e2UFPNOmx+fsIl4Urigw==
+@inquirer/prompts@8.7.3 | development | MIT
+ Lockfile path: node_modules/@inquirer/prompts
+ Package: https://www.npmjs.com/package/@inquirer/prompts/v/8.7.3
+ Archive: https://registry.npmjs.org/@inquirer/prompts/-/prompts-8.7.3.tgz
+ Integrity: sha512-DHK20kO+H5k/XcJQpHQUpVQbMYHHhZchlGWhZVMUV4WIuRuBW5744hipOtriE1vMqPLgnIyXFMHWKkIbTepa4Q==
+@inquirer/rawlist@5.3.6 | development | MIT
+ Lockfile path: node_modules/@inquirer/rawlist
+ Package: https://www.npmjs.com/package/@inquirer/rawlist/v/5.3.6
+ Archive: https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-5.3.6.tgz
+ Integrity: sha512-2exExhQ5xTBqfN/rRlZkvM1Gm3b0eiLcWe2NqxWxe7qYmnU4M3j/n9TwzfPUb+Q7dAAz9TmBtDWWdF3jbp4ZLA==
+@inquirer/search@4.3.4 | development | MIT
+ Lockfile path: node_modules/@inquirer/search
+ Package: https://www.npmjs.com/package/@inquirer/search/v/4.3.4
+ Archive: https://registry.npmjs.org/@inquirer/search/-/search-4.3.4.tgz
+ Integrity: sha512-xsk0UeFP04jsbEeFMzghjUaH2aHXyt+WykSajQzq/8qyBNHsgGYHea/sFwv5T08kW7Sp+KDX8aF124zPUIs1Ww==
+@inquirer/select@5.2.6 | development | MIT
+ Lockfile path: node_modules/@inquirer/select
+ Package: https://www.npmjs.com/package/@inquirer/select/v/5.2.6
+ Archive: https://registry.npmjs.org/@inquirer/select/-/select-5.2.6.tgz
+ Integrity: sha512-jFu7CFghlr0vpi/326a4wz6BGkJH9X3H/iIu+ahyV2Ja52ktqQE3fC9BYP47JKeSPQYGUv5xquE2Vp9PbepZ3g==
+@inquirer/type@4.2.0 | development | MIT
+ Lockfile path: node_modules/@inquirer/type
+ Package: https://www.npmjs.com/package/@inquirer/type/v/4.2.0
+ Archive: https://registry.npmjs.org/@inquirer/type/-/type-4.2.0.tgz
+ Integrity: sha512-2rnHX8RPo4glJYqnFc/v3nQeIhDcjyRRyaT7MGcfL/8xv/8K03SXYoA2tX06WVSKywWOE/ncbG3dtuzLjR5TCQ==
+@jridgewell/gen-mapping@0.4.0-beta.0 | development | MIT
+ Lockfile path: node_modules/@jridgewell/gen-mapping
+ Package: https://www.npmjs.com/package/@jridgewell/gen-mapping/v/0.4.0-beta.0
+ Archive: https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.4.0-beta.0.tgz
+ Integrity: sha512-JdGNkbE4GlNPYQhM0L95fBQr7ctLZJ276QXQLTad4t1oSdnnCI3fDq9DW3BqYAWv8Wc3+HS+4Gsii1oPMCfz1w==
@jridgewell/resolve-uri@3.1.2 | runtime | MIT
Lockfile path: node_modules/@jridgewell/resolve-uri
Package: https://www.npmjs.com/package/@jridgewell/resolve-uri/v/3.1.2
@@ -865,6 +1165,11 @@ commander@13.1.0 | runtime | MIT
Package: https://www.npmjs.com/package/@scalar/openapi-types/v/0.8.0
Archive: https://registry.npmjs.org/@scalar/openapi-types/-/openapi-types-0.8.0.tgz
Integrity: sha512-WmaxVSfvY5K/TwcG2B2TU1WOe1As1uc2s7myswtP6dBlcjU3hM08SApxv/jmyGaCE8t4gO5BBhmHY4pDUfmr2g==
+@sec-ant/readable-stream@0.4.1 | development | MIT
+ Lockfile path: node_modules/@sec-ant/readable-stream
+ Package: https://www.npmjs.com/package/@sec-ant/readable-stream/v/0.4.1
+ Archive: https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz
+ Integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==
@shikijs/core@2.5.0 | development | MIT
Lockfile path: node_modules/@shikijs/core
Package: https://www.npmjs.com/package/@shikijs/core/v/2.5.0
@@ -905,6 +1210,41 @@ commander@13.1.0 | runtime | MIT
Package: https://www.npmjs.com/package/@shikijs/vscode-textmate/v/10.0.2
Archive: https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz
Integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==
+@sindresorhus/merge-streams@4.0.0 | development | MIT
+ Lockfile path: node_modules/@sindresorhus/merge-streams
+ Package: https://www.npmjs.com/package/@sindresorhus/merge-streams/v/4.0.0
+ Archive: https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz
+ Integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==
+@stryker-mutator/api@10.0.0 | development | Apache-2.0
+ Lockfile path: node_modules/@stryker-mutator/api
+ Package: https://www.npmjs.com/package/@stryker-mutator/api/v/10.0.0
+ Archive: https://registry.npmjs.org/@stryker-mutator/api/-/api-10.0.0.tgz
+ Integrity: sha512-ZtAJ0ZT3MVRCWJTBE2h90XB/6E+4lifHYtcTyNG6nU2nLekPgTo4gD5esjX6Okxo1b/JB4jJzyxYB54fwKAoJw==
+@stryker-mutator/core@10.0.0 | development | Apache-2.0
+ Lockfile path: node_modules/@stryker-mutator/core
+ Package: https://www.npmjs.com/package/@stryker-mutator/core/v/10.0.0
+ Archive: https://registry.npmjs.org/@stryker-mutator/core/-/core-10.0.0.tgz
+ Integrity: sha512-ZvMsRyaXQQ5e6Thcid9pkuODv6Fn9E3nrBQJUap+hcJuGJ4unm26afo3m6YKSjn8kinyxJ/3TXf0cTWRDaTxVw==
+@stryker-mutator/instrumenter@10.0.0 | development | Apache-2.0
+ Lockfile path: node_modules/@stryker-mutator/instrumenter
+ Package: https://www.npmjs.com/package/@stryker-mutator/instrumenter/v/10.0.0
+ Archive: https://registry.npmjs.org/@stryker-mutator/instrumenter/-/instrumenter-10.0.0.tgz
+ Integrity: sha512-B7Wmn1KlEWyFeOz6D6oGvQGRfi5Xw3VemG6dEKvFQp4qLvxD9Mf4kcZghfxffgnYwXd3bFgqXsJ+ZGlhdfIOrQ==
+@stryker-mutator/typescript-checker@10.0.0 | development | Apache-2.0
+ Lockfile path: node_modules/@stryker-mutator/typescript-checker
+ Package: https://www.npmjs.com/package/@stryker-mutator/typescript-checker/v/10.0.0
+ Archive: https://registry.npmjs.org/@stryker-mutator/typescript-checker/-/typescript-checker-10.0.0.tgz
+ Integrity: sha512-GLvC0LVd8RJ5SxR3eFcgb7DFKmw07DYtPXs942z6Qh7l6p7YBnFyd7lPw7nvA9UKEYDd22embrFmLlyC1XJjXw==
+@stryker-mutator/util@10.0.0 | development | Apache-2.0
+ Lockfile path: node_modules/@stryker-mutator/util
+ Package: https://www.npmjs.com/package/@stryker-mutator/util/v/10.0.0
+ Archive: https://registry.npmjs.org/@stryker-mutator/util/-/util-10.0.0.tgz
+ Integrity: sha512-LzOpHiJaCp2ABQgnPMlrQQcsK43bd5Vo/2FGL78aN62yDoeRQ+4j3tzeuXxK5OAHdC3fUz6TDoy4IsoAuLAd3w==
+@stryker-mutator/vitest-runner@10.0.0 | development | Apache-2.0
+ Lockfile path: node_modules/@stryker-mutator/vitest-runner
+ Package: https://www.npmjs.com/package/@stryker-mutator/vitest-runner/v/10.0.0
+ Archive: https://registry.npmjs.org/@stryker-mutator/vitest-runner/-/vitest-runner-10.0.0.tgz
+ Integrity: sha512-SHK2/vfvRUpiz7jXPnQMBnr6zLdm69DK03Mo5mPhaZWcRSygrKUqYsPqWsXsK+5ySHzlMTfCyFK5NQ/X9sJFFw==
@toon-format/toon@2.3.1 | runtime | MIT
Lockfile path: node_modules/@toon-format/toon
Package: https://www.npmjs.com/package/@toon-format/toon/v/2.3.1
@@ -965,11 +1305,21 @@ commander@13.1.0 | runtime | MIT
Package: https://www.npmjs.com/package/@types/estree/v/1.0.9
Archive: https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz
Integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==
+@types/gensync@1.0.5 | development | MIT
+ Lockfile path: node_modules/@types/gensync
+ Package: https://www.npmjs.com/package/@types/gensync/v/1.0.5
+ Archive: https://registry.npmjs.org/@types/gensync/-/gensync-1.0.5.tgz
+ Integrity: sha512-MbsRCT7mTikHwKZ0X+LVUTLRrZZRLipTuXEO9qOYO+zmjMVk81axyClMROf6uoPD9MRVu46bx8zoR0Ad9q3NAg==
@types/hast@3.0.5 | development | MIT
Lockfile path: node_modules/@types/hast
Package: https://www.npmjs.com/package/@types/hast/v/3.0.5
Archive: https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz
Integrity: sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==
+@types/jsesc@2.5.1 | development | MIT
+ Lockfile path: node_modules/@types/jsesc
+ Package: https://www.npmjs.com/package/@types/jsesc/v/2.5.1
+ Archive: https://registry.npmjs.org/@types/jsesc/-/jsesc-2.5.1.tgz
+ Integrity: sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==
@types/linkify-it@5.0.0 | development | MIT
Lockfile path: node_modules/@types/linkify-it
Package: https://www.npmjs.com/package/@types/linkify-it/v/5.0.0
@@ -1325,6 +1675,11 @@ algoliasearch@5.59.0 | development | MIT
Package: https://www.npmjs.com/package/algoliasearch/v/5.59.0
Archive: https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.59.0.tgz
Integrity: sha512-wUXzaeI7B526W4y1gFg3lcxgDZ67XSgRJIiellYWOas/pLpO7rOtxm9Gr2E/C8aiSDXIgx1q5TdSsvK67Uakqw==
+angular-html-parser@10.11.0 | development | MIT
+ Lockfile path: node_modules/angular-html-parser
+ Package: https://www.npmjs.com/package/angular-html-parser/v/10.11.0
+ Archive: https://registry.npmjs.org/angular-html-parser/-/angular-html-parser-10.11.0.tgz
+ Integrity: sha512-3vERzJ65UFDr3C7uozLJwsNcQS3FS784dSh583oDgDTTZMgXe3/pdyXgKndxiP5R2lvYRfW6gSl145Hnyf2OFA==
ansi-escapes@7.3.0 | development | MIT
Lockfile path: node_modules/ansi-escapes
Package: https://www.npmjs.com/package/ansi-escapes/v/7.3.0
@@ -1375,6 +1730,11 @@ base64-js@1.5.1 | runtime | MIT
Package: https://www.npmjs.com/package/base64-js/v/1.5.1
Archive: https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz
Integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
+baseline-browser-mapping@2.11.28 | development | Apache-2.0
+ Lockfile path: node_modules/baseline-browser-mapping
+ Package: https://www.npmjs.com/package/baseline-browser-mapping/v/2.11.28
+ Archive: https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.28.tgz
+ Integrity: sha512-br0LOmjJqr6Rt4AEkadbcNf67Zpo7EPwKsWbjnRrJIa1QYFHQBqM4C7F2WTbV8ahTEcg1Eo+Y0aWjjORInYFyA==
bindings@1.5.0 | runtime, optional | MIT
Lockfile path: node_modules/bindings
Package: https://www.npmjs.com/package/bindings/v/1.5.0
@@ -1405,6 +1765,11 @@ brace-expansion@5.0.12 | runtime | MIT
Package: https://www.npmjs.com/package/brace-expansion/v/5.0.12
Archive: https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz
Integrity: sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==
+browserslist@4.29.3 | development | MIT
+ Lockfile path: node_modules/browserslist
+ Package: https://www.npmjs.com/package/browserslist/v/4.29.3
+ Archive: https://registry.npmjs.org/browserslist/-/browserslist-4.29.3.tgz
+ Integrity: sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA==
buffer@6.0.3 | runtime | MIT
Lockfile path: node_modules/buffer
Package: https://www.npmjs.com/package/buffer/v/6.0.3
@@ -1425,6 +1790,11 @@ call-bound@1.0.4 | runtime | MIT
Package: https://www.npmjs.com/package/call-bound/v/1.0.4
Archive: https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz
Integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==
+caniuse-lite@1.0.30001815 | development | CC-BY-4.0
+ Lockfile path: node_modules/caniuse-lite
+ Package: https://www.npmjs.com/package/caniuse-lite/v/1.0.30001815
+ Archive: https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001815.tgz
+ Integrity: sha512-uDybUEyfgbyJJ93J1vikTylBX31AkdVYeVPeffMQpf8XN7hSfr0oZOn4VFOIMNvtU5+rHvC5Udg2TgtJKHm23Q==
canonicalize@3.0.0 | runtime | Apache-2.0
Lockfile path: node_modules/canonicalize
Package: https://www.npmjs.com/package/canonicalize/v/3.0.0
@@ -1445,6 +1815,11 @@ chai@6.3.0 | development | MIT
Package: https://www.npmjs.com/package/chai/v/6.3.0
Archive: https://registry.npmjs.org/chai/-/chai-6.3.0.tgz
Integrity: sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==
+chalk@5.6.2 | development | MIT
+ Lockfile path: node_modules/chalk
+ Package: https://www.npmjs.com/package/chalk/v/5.6.2
+ Archive: https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz
+ Integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==
character-entities-html4@2.1.0 | development | MIT
Lockfile path: node_modules/character-entities-html4
Package: https://www.npmjs.com/package/character-entities-html4/v/2.1.0
@@ -1455,6 +1830,11 @@ character-entities-legacy@3.0.0 | development | MIT
Package: https://www.npmjs.com/package/character-entities-legacy/v/3.0.0
Archive: https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz
Integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==
+chardet@2.2.0 | development | MIT
+ Lockfile path: node_modules/chardet
+ Package: https://www.npmjs.com/package/chardet/v/2.2.0
+ Archive: https://registry.npmjs.org/chardet/-/chardet-2.2.0.tgz
+ Integrity: sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==
chownr@1.1.4 | runtime, optional | ISC
Lockfile path: node_modules/chownr
Package: https://www.npmjs.com/package/chownr/v/1.1.4
@@ -1475,6 +1855,11 @@ cli-truncate@5.2.0 | development | MIT
Package: https://www.npmjs.com/package/cli-truncate/v/5.2.0
Archive: https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz
Integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==
+cli-width@4.1.0 | development | ISC
+ Lockfile path: node_modules/cli-width
+ Package: https://www.npmjs.com/package/cli-width/v/4.1.0
+ Archive: https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz
+ Integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==
colorette@2.0.20 | development | MIT
Lockfile path: node_modules/colorette
Package: https://www.npmjs.com/package/colorette/v/2.0.20
@@ -1490,6 +1875,11 @@ commander@14.0.3 | development | MIT
Package: https://www.npmjs.com/package/commander/v/14.0.3
Archive: https://registry.npmjs.org/commander/-/commander-14.0.3.tgz
Integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==
+convert-source-map@2.0.0 | development | MIT
+ Lockfile path: node_modules/convert-source-map
+ Package: https://www.npmjs.com/package/convert-source-map/v/2.0.0
+ Archive: https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz
+ Integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
copy-anything@4.1.5 | development | MIT
Lockfile path: node_modules/copy-anything
Package: https://www.npmjs.com/package/copy-anything/v/4.1.5
@@ -1530,6 +1920,11 @@ dequal@2.0.3 | development | MIT
Package: https://www.npmjs.com/package/dequal/v/2.0.3
Archive: https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz
Integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==
+des.js@1.1.0 | development | MIT
+ Lockfile path: node_modules/des.js
+ Package: https://www.npmjs.com/package/des.js/v/1.1.0
+ Archive: https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz
+ Integrity: sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==
detect-libc@2.1.2 | runtime | Apache-2.0
Lockfile path: node_modules/detect-libc
Package: https://www.npmjs.com/package/detect-libc/v/2.1.2
@@ -1545,6 +1940,11 @@ diff@9.0.0 | runtime | BSD-3-Clause
Package: https://www.npmjs.com/package/diff/v/9.0.0
Archive: https://registry.npmjs.org/diff/-/diff-9.0.0.tgz
Integrity: sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==
+diff-match-patch@1.0.5 | development | Apache-2.0
+ Lockfile path: node_modules/diff-match-patch
+ Package: https://www.npmjs.com/package/diff-match-patch/v/1.0.5
+ Archive: https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz
+ Integrity: sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==
diff3@0.0.3 | runtime | MIT
Lockfile path: node_modules/diff3
Package: https://www.npmjs.com/package/diff3/v/0.0.3
@@ -1555,6 +1955,11 @@ dunder-proto@1.0.1 | runtime | MIT
Package: https://www.npmjs.com/package/dunder-proto/v/1.0.1
Archive: https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz
Integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==
+electron-to-chromium@1.5.452 | development | ISC
+ Lockfile path: node_modules/electron-to-chromium
+ Package: https://www.npmjs.com/package/electron-to-chromium/v/1.5.452
+ Archive: https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.452.tgz
+ Integrity: sha512-cVvk1YBSj6571VmV6hdJTqj94Y79xSu8cj6Y9t6ae4Y4431z8/7EJFvu3rVp90xhfuv9BpPKUmaLfTo4+FOGBg==
emoji-regex@10.6.0 | development | MIT
Lockfile path: node_modules/emoji-regex
Package: https://www.npmjs.com/package/emoji-regex/v/10.6.0
@@ -1565,6 +1970,11 @@ emoji-regex-xs@1.0.0 | development | MIT
Package: https://www.npmjs.com/package/emoji-regex-xs/v/1.0.0
Archive: https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz
Integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==
+empathic@2.1.0 | development | MIT
+ Lockfile path: node_modules/empathic
+ Package: https://www.npmjs.com/package/empathic/v/2.1.0
+ Archive: https://registry.npmjs.org/empathic/-/empathic-2.1.0.tgz
+ Integrity: sha512-AnfC1ATldl49/cvZdLPDjBfrRNwbDO05aibiOtzQu3qtlbJtomNLhF30HEtn/7iBz50dlMECqATo3fG0LrdEgw==
end-of-stream@1.4.5 | runtime, optional | MIT
Lockfile path: node_modules/end-of-stream
Package: https://www.npmjs.com/package/end-of-stream/v/1.4.5
@@ -1600,6 +2010,11 @@ es-object-atoms@1.1.2 | runtime | MIT
Package: https://www.npmjs.com/package/es-object-atoms/v/1.1.2
Archive: https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz
Integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==
+escalade@3.2.0 | development | MIT
+ Lockfile path: node_modules/escalade
+ Package: https://www.npmjs.com/package/escalade/v/3.2.0
+ Archive: https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz
+ Integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==
estree-walker@3.0.3 | development | MIT
Lockfile path: node_modules/estree-walker
Package: https://www.npmjs.com/package/estree-walker/v/3.0.3
@@ -1635,6 +2050,11 @@ evmole@0.9.3 | runtime | MIT
Package: https://www.npmjs.com/package/evmole/v/0.9.3
Archive: https://registry.npmjs.org/evmole/-/evmole-0.9.3.tgz
Integrity: sha512-UhO5HMAoEPCupSxvBJPlm/L0Qk/KVJ/OJo3r4EyhXADIEHqU5aTkAzsBfzk4lXGjqxVnaYbh1GPSmZtmSHFnQQ==
+execa@9.6.1 | development | MIT
+ Lockfile path: node_modules/execa
+ Package: https://www.npmjs.com/package/execa/v/9.6.1
+ Archive: https://registry.npmjs.org/execa/-/execa-9.6.1.tgz
+ Integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==
expand-template@2.0.3 | runtime, optional | (MIT OR WTFPL)
Lockfile path: node_modules/expand-template
Package: https://www.npmjs.com/package/expand-template/v/2.0.3
@@ -1685,11 +2105,21 @@ fdir@6.5.0 | development | MIT
Package: https://www.npmjs.com/package/fdir/v/6.5.0
Archive: https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz
Integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==
+figures@6.1.0 | development | MIT
+ Lockfile path: node_modules/figures
+ Package: https://www.npmjs.com/package/figures/v/6.1.0
+ Archive: https://registry.npmjs.org/figures/-/figures-6.1.0.tgz
+ Integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==
file-uri-to-path@1.0.0 | runtime, optional | MIT
Lockfile path: node_modules/file-uri-to-path
Package: https://www.npmjs.com/package/file-uri-to-path/v/1.0.0
Archive: https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz
Integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==
+flru@1.0.2 | development | MIT
+ Lockfile path: node_modules/flru
+ Package: https://www.npmjs.com/package/flru/v/1.0.2
+ Archive: https://registry.npmjs.org/flru/-/flru-1.0.2.tgz
+ Integrity: sha512-kWyh8ADvHBFz6ua5xYOPnUroZTT/bwWfrCeL0Wj1dzG4/YOmOcfJ99W8dOVyyynJN35rZ9aCOtHChqQovV7yog==
focus-trap@7.8.0 | development | MIT
Lockfile path: node_modules/focus-trap
Package: https://www.npmjs.com/package/focus-trap/v/7.8.0
@@ -1725,6 +2155,11 @@ function-bind@1.1.2 | runtime | MIT
Package: https://www.npmjs.com/package/function-bind/v/1.1.2
Archive: https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz
Integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
+gensync@1.0.0-beta.2 | development | MIT
+ Lockfile path: node_modules/gensync
+ Package: https://www.npmjs.com/package/gensync/v/1.0.0-beta.2
+ Archive: https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz
+ Integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
get-east-asian-width@1.6.0 | development | MIT
Lockfile path: node_modules/get-east-asian-width
Package: https://www.npmjs.com/package/get-east-asian-width/v/1.6.0
@@ -1740,6 +2175,11 @@ get-proto@1.0.1 | runtime | MIT
Package: https://www.npmjs.com/package/get-proto/v/1.0.1
Archive: https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz
Integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==
+get-stream@9.0.1 | development | MIT
+ Lockfile path: node_modules/get-stream
+ Package: https://www.npmjs.com/package/get-stream/v/9.0.1
+ Archive: https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz
+ Integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==
get-tsconfig@4.14.3 | development | MIT
Lockfile path: node_modules/get-tsconfig
Package: https://www.npmjs.com/package/get-tsconfig/v/4.14.3
@@ -1805,11 +2245,21 @@ html-void-elements@3.0.0 | development | MIT
Package: https://www.npmjs.com/package/html-void-elements/v/3.0.0
Archive: https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz
Integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==
+human-signals@8.0.1 | development | Apache-2.0
+ Lockfile path: node_modules/human-signals
+ Package: https://www.npmjs.com/package/human-signals/v/8.0.1
+ Archive: https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz
+ Integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==
husky@9.1.7 | development | MIT
Lockfile path: node_modules/husky
Package: https://www.npmjs.com/package/husky/v/9.1.7
Archive: https://registry.npmjs.org/husky/-/husky-9.1.7.tgz
Integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==
+iconv-lite@0.7.3 | development | MIT
+ Lockfile path: node_modules/iconv-lite
+ Package: https://www.npmjs.com/package/iconv-lite/v/0.7.3
+ Archive: https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz
+ Integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==
ieee754@1.2.1 | runtime | BSD-3-Clause
Lockfile path: node_modules/ieee754
Package: https://www.npmjs.com/package/ieee754/v/1.2.1
@@ -1820,6 +2270,11 @@ ignore@7.0.6 | runtime | MIT
Package: https://www.npmjs.com/package/ignore/v/7.0.6
Archive: https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz
Integrity: sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==
+import-meta-resolve@4.2.0 | development | MIT
+ Lockfile path: node_modules/import-meta-resolve
+ Package: https://www.npmjs.com/package/import-meta-resolve/v/4.2.0
+ Archive: https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz
+ Integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==
imurmurhash@0.1.4 | runtime | MIT
Lockfile path: node_modules/imurmurhash
Package: https://www.npmjs.com/package/imurmurhash/v/0.1.4
@@ -1855,11 +2310,26 @@ is-fullwidth-code-point@5.1.0 | development | MIT
Package: https://www.npmjs.com/package/is-fullwidth-code-point/v/5.1.0
Archive: https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz
Integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==
+is-plain-obj@4.1.0 | development | MIT
+ Lockfile path: node_modules/is-plain-obj
+ Package: https://www.npmjs.com/package/is-plain-obj/v/4.1.0
+ Archive: https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz
+ Integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==
+is-stream@4.0.1 | development | MIT
+ Lockfile path: node_modules/is-stream
+ Package: https://www.npmjs.com/package/is-stream/v/4.0.1
+ Archive: https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz
+ Integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==
is-typed-array@1.1.15 | runtime | MIT
Lockfile path: node_modules/is-typed-array
Package: https://www.npmjs.com/package/is-typed-array/v/1.1.15
Archive: https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz
Integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==
+is-unicode-supported@2.1.0 | development | MIT
+ Lockfile path: node_modules/is-unicode-supported
+ Package: https://www.npmjs.com/package/is-unicode-supported/v/2.1.0
+ Archive: https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz
+ Integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==
isarray@2.0.5 | runtime | MIT
Lockfile path: node_modules/isarray
Package: https://www.npmjs.com/package/isarray/v/2.0.5
@@ -1890,6 +2360,11 @@ jose@6.2.3 | runtime | MIT
Package: https://www.npmjs.com/package/jose/v/6.2.3
Archive: https://registry.npmjs.org/jose/-/jose-6.2.3.tgz
Integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==
+js-md4@0.3.2 | development | MIT
+ Lockfile path: node_modules/js-md4
+ Package: https://www.npmjs.com/package/js-md4/v/0.3.2
+ Archive: https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz
+ Integrity: sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==
js-tokens@10.0.0 | development | MIT
Lockfile path: node_modules/js-tokens
Package: https://www.npmjs.com/package/js-tokens/v/10.0.0
@@ -1930,11 +2405,26 @@ jscpd-windows-x64-msvc@5.0.12 | development, optional | MIT
Package: https://www.npmjs.com/package/jscpd-windows-x64-msvc/v/5.0.12
Archive: https://registry.npmjs.org/jscpd-windows-x64-msvc/-/jscpd-windows-x64-msvc-5.0.12.tgz
Integrity: sha512-01x1klKjvfzI6Of5tI9u72x7TIQZQLLG6kMdsEPJKl/BXbskdknrfCepeTXRBbdFPKs25jfcd0klZ4OdDvww5w==
+jsesc@3.1.0 | development | MIT
+ Lockfile path: node_modules/jsesc
+ Package: https://www.npmjs.com/package/jsesc/v/3.1.0
+ Archive: https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz
+ Integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==
+json-rpc-2.0@1.8.1 | development | MIT
+ Lockfile path: node_modules/json-rpc-2.0
+ Package: https://www.npmjs.com/package/json-rpc-2.0/v/1.8.1
+ Archive: https://registry.npmjs.org/json-rpc-2.0/-/json-rpc-2.0-1.8.1.tgz
+ Integrity: sha512-BmN42JKn6FI36pcMMzHCXQhpKNEfbeqgSIcUYHKnifq0/4Ey6PDinovwUOgajT7NsgUtpWdDXaCWlpI4LHXN3g==
json-schema-traverse@1.0.0 | runtime | MIT
Lockfile path: node_modules/json-schema-traverse
Package: https://www.npmjs.com/package/json-schema-traverse/v/1.0.0
Archive: https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz
Integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
+json5@2.2.3 | development | MIT
+ Lockfile path: node_modules/json5
+ Package: https://www.npmjs.com/package/json5/v/2.2.3
+ Archive: https://registry.npmjs.org/json5/-/json5-2.2.3.tgz
+ Integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
jsonc-parser@3.3.1 | runtime | MIT
Lockfile path: node_modules/jsonc-parser
Package: https://www.npmjs.com/package/jsonc-parser/v/3.3.1
@@ -2020,6 +2510,11 @@ listr2@9.0.5 | development | MIT
Package: https://www.npmjs.com/package/listr2/v/9.0.5
Archive: https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz
Integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==
+lodash.groupby@4.6.0 | development | MIT
+ Lockfile path: node_modules/lodash.groupby
+ Package: https://www.npmjs.com/package/lodash.groupby/v/4.6.0
+ Archive: https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz
+ Integrity: sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==
log-update@6.1.0 | development | MIT
Lockfile path: node_modules/log-update
Package: https://www.npmjs.com/package/log-update/v/6.1.0
@@ -2120,6 +2615,11 @@ mimic-response@3.1.0 | runtime | MIT
Package: https://www.npmjs.com/package/mimic-response/v/3.1.0
Archive: https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz
Integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==
+minimalistic-assert@1.0.1 | development | ISC
+ Lockfile path: node_modules/minimalistic-assert
+ Package: https://www.npmjs.com/package/minimalistic-assert/v/1.0.1
+ Archive: https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz
+ Integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
minimatch@10.2.5 | runtime | BlueOak-1.0.0
Lockfile path: node_modules/minimatch
Package: https://www.npmjs.com/package/minimatch/v/10.2.5
@@ -2155,6 +2655,31 @@ mkdirp-classic@0.5.3 | runtime, optional | MIT
Package: https://www.npmjs.com/package/mkdirp-classic/v/0.5.3
Archive: https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz
Integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
+mutation-server-protocol@0.4.1 | development | Apache-2.0
+ Lockfile path: node_modules/mutation-server-protocol
+ Package: https://www.npmjs.com/package/mutation-server-protocol/v/0.4.1
+ Archive: https://registry.npmjs.org/mutation-server-protocol/-/mutation-server-protocol-0.4.1.tgz
+ Integrity: sha512-SBGK0j8hLDne7bktgThKI8kGvGTx3rY3LAeQTmOKZ5bVnL/7TorLMvcVF7dIPJCu5RNUWhkkuF53kurygYVt3g==
+mutation-testing-elements@3.8.4 | development | Apache-2.0
+ Lockfile path: node_modules/mutation-testing-elements
+ Package: https://www.npmjs.com/package/mutation-testing-elements/v/3.8.4
+ Archive: https://registry.npmjs.org/mutation-testing-elements/-/mutation-testing-elements-3.8.4.tgz
+ Integrity: sha512-5CF1SNa7at5ZH33vEr+21wNebTSrtNIVvnzaUlxortHajOrIPaSLczIWvg6sI/fsExekQ7jookwf2cHftkckqQ==
+mutation-testing-metrics@3.8.4 | development | Apache-2.0
+ Lockfile path: node_modules/mutation-testing-metrics
+ Package: https://www.npmjs.com/package/mutation-testing-metrics/v/3.8.4
+ Archive: https://registry.npmjs.org/mutation-testing-metrics/-/mutation-testing-metrics-3.8.4.tgz
+ Integrity: sha512-DZcmndJBH6nrNs3tpiB3OcMVq9KkG2cHCpJSnDxSxPwi9qrafRmoec40xjhkbzOoX1n7/4UkDqg5tIj4A6nvCw==
+mutation-testing-report-schema@3.8.4 | development | Apache-2.0
+ Lockfile path: node_modules/mutation-testing-report-schema
+ Package: https://www.npmjs.com/package/mutation-testing-report-schema/v/3.8.4
+ Archive: https://registry.npmjs.org/mutation-testing-report-schema/-/mutation-testing-report-schema-3.8.4.tgz
+ Integrity: sha512-s4G71R6Lt/PpZ0cqeglIcgyBdzLM8E+SeCHZAPg1wkSsPtRBa4XfPzAozYKdiJk/TLbNEEb7En9t0/bveuPuxA==
+mute-stream@3.0.0 | development | ISC
+ Lockfile path: node_modules/mute-stream
+ Package: https://www.npmjs.com/package/mute-stream/v/3.0.0
+ Archive: https://registry.npmjs.org/mute-stream/-/mute-stream-3.0.0.tgz
+ Integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==
nanoid@3.3.20 | development | MIT
Lockfile path: node_modules/nanoid
Package: https://www.npmjs.com/package/nanoid/v/3.3.20
@@ -2170,6 +2695,26 @@ node-abi@3.98.0 | runtime, optional | MIT
Package: https://www.npmjs.com/package/node-abi/v/3.98.0
Archive: https://registry.npmjs.org/node-abi/-/node-abi-3.98.0.tgz
Integrity: sha512-Llh8noNw7s15m+rmIrZnZQbe4gLyasL9loT74rC92PkmwWKeEedKvc7o6pjp8B3MhrYT/Cl846NOYZbz1wmddw==
+node-releases@2.0.58 | development | MIT
+ Lockfile path: node_modules/node-releases
+ Package: https://www.npmjs.com/package/node-releases/v/2.0.58
+ Archive: https://registry.npmjs.org/node-releases/-/node-releases-2.0.58.tgz
+ Integrity: sha512-BdiKAgbfRlvfMq/XzO/AF9NPmaA8a+YpLAyAFTVFBj3gARrAIfWvNq7Q7NE4se3KgFu0KkrvQMxTS0XVnsGzHg==
+npm-run-path@6.0.0 | development | MIT
+ Lockfile path: node_modules/npm-run-path
+ Package: https://www.npmjs.com/package/npm-run-path/v/6.0.0
+ Archive: https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz
+ Integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==
+path-key@4.0.0 | development | MIT
+ Lockfile path: node_modules/npm-run-path/node_modules/path-key
+ Package: https://www.npmjs.com/package/path-key/v/4.0.0
+ Archive: https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz
+ Integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==
+object-inspect@1.13.4 | development | MIT
+ Lockfile path: node_modules/object-inspect
+ Package: https://www.npmjs.com/package/object-inspect/v/1.13.4
+ Archive: https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz
+ Integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==
obug@2.2.1 | development | MIT
Lockfile path: node_modules/obug
Package: https://www.npmjs.com/package/obug/v/2.2.1
@@ -2230,6 +2775,11 @@ pako@1.0.11 | runtime | (MIT AND Zlib)
Package: https://www.npmjs.com/package/pako/v/1.0.11
Archive: https://registry.npmjs.org/pako/-/pako-1.0.11.tgz
Integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
+parse-ms@4.0.0 | development | MIT
+ Lockfile path: node_modules/parse-ms
+ Package: https://www.npmjs.com/package/parse-ms/v/4.0.0
+ Archive: https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz
+ Integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==
parse5@8.0.1 | runtime | MIT
Lockfile path: node_modules/parse5
Package: https://www.npmjs.com/package/parse5/v/8.0.1
@@ -2320,6 +2870,11 @@ prebuild-install@7.1.3 | runtime, optional | MIT
Package: https://www.npmjs.com/package/prebuild-install/v/7.1.3
Archive: https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz
Integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==
+pretty-ms@9.3.1 | development | MIT
+ Lockfile path: node_modules/pretty-ms
+ Package: https://www.npmjs.com/package/pretty-ms/v/9.3.1
+ Archive: https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.1.tgz
+ Integrity: sha512-HzMy3Geq23nVALD/M2LliU+F+M+gVNsvkQWWqeBZ8HDiCgzo6YPJ/Omrmtq24EFrIsk0a3EkQGEd7bDOo+IhGA==
process@0.11.10 | runtime | MIT
Lockfile path: node_modules/process
Package: https://www.npmjs.com/package/process/v/0.11.10
@@ -2330,6 +2885,11 @@ process-warning@5.0.0 | runtime | MIT
Package: https://www.npmjs.com/package/process-warning/v/5.0.0
Archive: https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz
Integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==
+progress@2.0.3 | development | MIT
+ Lockfile path: node_modules/progress
+ Package: https://www.npmjs.com/package/progress/v/2.0.3
+ Archive: https://registry.npmjs.org/progress/-/progress-2.0.3.tgz
+ Integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
property-information@7.2.0 | development | MIT
Lockfile path: node_modules/property-information
Package: https://www.npmjs.com/package/property-information/v/7.2.0
@@ -2345,6 +2905,11 @@ pure-rand@8.4.2 | development | MIT
Package: https://www.npmjs.com/package/pure-rand/v/8.4.2
Archive: https://registry.npmjs.org/pure-rand/-/pure-rand-8.4.2.tgz
Integrity: sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng==
+qs@6.15.1 | development | BSD-3-Clause
+ Lockfile path: node_modules/qs
+ Package: https://www.npmjs.com/package/qs/v/6.15.1
+ Archive: https://registry.npmjs.org/qs/-/qs-6.15.1.tgz
+ Integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==
quick-format-unescaped@4.0.4 | runtime | MIT
Lockfile path: node_modules/quick-format-unescaped
Package: https://www.npmjs.com/package/quick-format-unescaped/v/4.0.4
@@ -2415,6 +2980,11 @@ rollup@4.64.1 | development | MIT
Package: https://www.npmjs.com/package/rollup/v/4.64.1
Archive: https://registry.npmjs.org/rollup/-/rollup-4.64.1.tgz
Integrity: sha512-7s4V+3J8ux75WNj9qwSJnrWmlCmZ2YHr2fojtHy5RiIMvhJv6DGe7HKKz7L6G1sq1Eg1fLC8SjYtAtDis1F4CQ==
+rxjs@7.8.2 | development | Apache-2.0
+ Lockfile path: node_modules/rxjs
+ Package: https://www.npmjs.com/package/rxjs/v/7.8.2
+ Archive: https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz
+ Integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==
safe-buffer@5.2.1 | runtime | MIT
Lockfile path: node_modules/safe-buffer
Package: https://www.npmjs.com/package/safe-buffer/v/5.2.1
@@ -2425,6 +2995,11 @@ safe-stable-stringify@2.5.0 | runtime | MIT
Package: https://www.npmjs.com/package/safe-stable-stringify/v/2.5.0
Archive: https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz
Integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==
+safer-buffer@2.1.2 | development | MIT
+ Lockfile path: node_modules/safer-buffer
+ Package: https://www.npmjs.com/package/safer-buffer/v/2.1.2
+ Archive: https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz
+ Integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
search-insights@2.17.3 | development | MIT
Lockfile path: node_modules/search-insights
Package: https://www.npmjs.com/package/search-insights/v/2.17.3
@@ -2460,6 +3035,26 @@ shiki@2.5.0 | development | MIT
Package: https://www.npmjs.com/package/shiki/v/2.5.0
Archive: https://registry.npmjs.org/shiki/-/shiki-2.5.0.tgz
Integrity: sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==
+side-channel@1.1.1 | development | MIT
+ Lockfile path: node_modules/side-channel
+ Package: https://www.npmjs.com/package/side-channel/v/1.1.1
+ Archive: https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz
+ Integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==
+side-channel-list@1.0.1 | development | MIT
+ Lockfile path: node_modules/side-channel-list
+ Package: https://www.npmjs.com/package/side-channel-list/v/1.0.1
+ Archive: https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz
+ Integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==
+side-channel-map@1.0.1 | development | MIT
+ Lockfile path: node_modules/side-channel-map
+ Package: https://www.npmjs.com/package/side-channel-map/v/1.0.1
+ Archive: https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz
+ Integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==
+side-channel-weakmap@1.0.2 | development | MIT
+ Lockfile path: node_modules/side-channel-weakmap
+ Package: https://www.npmjs.com/package/side-channel-weakmap/v/1.0.2
+ Archive: https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz
+ Integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==
signal-exit@4.1.0 | runtime | ISC
Lockfile path: node_modules/signal-exit
Package: https://www.npmjs.com/package/signal-exit/v/4.1.0
@@ -2495,6 +3090,11 @@ sonic-boom@4.2.1 | runtime | MIT
Package: https://www.npmjs.com/package/sonic-boom/v/4.2.1
Archive: https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz
Integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==
+source-map@0.7.6 | development | BSD-3-Clause
+ Lockfile path: node_modules/source-map
+ Package: https://www.npmjs.com/package/source-map/v/0.7.6
+ Archive: https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz
+ Integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==
source-map-js@1.2.2 | development | BSD-3-Clause
Lockfile path: node_modules/source-map-js
Package: https://www.npmjs.com/package/source-map-js/v/1.2.2
@@ -2555,6 +3155,11 @@ strip-ansi@7.2.0 | development | MIT
Package: https://www.npmjs.com/package/strip-ansi/v/7.2.0
Archive: https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz
Integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==
+strip-final-newline@4.0.0 | development | MIT
+ Lockfile path: node_modules/strip-final-newline
+ Package: https://www.npmjs.com/package/strip-final-newline/v/4.0.0
+ Archive: https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz
+ Integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==
strip-json-comments@5.0.3 | development | MIT
Lockfile path: node_modules/strip-json-comments
Package: https://www.npmjs.com/package/strip-json-comments/v/5.0.3
@@ -2625,16 +3230,26 @@ tokenx@1.3.0 | runtime | MIT
Package: https://www.npmjs.com/package/tokenx/v/1.3.0
Archive: https://registry.npmjs.org/tokenx/-/tokenx-1.3.0.tgz
Integrity: sha512-NLdXTEZkKiO0gZuLtMoZKjCXTREXeZZt8nnnNeyoXtNZAfG/GKGSbQtLU5STspc0rMSwcA+UJfWZkbNU01iKmQ==
+tree-kill@1.2.2 | development | MIT
+ Lockfile path: node_modules/tree-kill
+ Package: https://www.npmjs.com/package/tree-kill/v/1.2.2
+ Archive: https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz
+ Integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
trim-lines@3.0.1 | development | MIT
Lockfile path: node_modules/trim-lines
Package: https://www.npmjs.com/package/trim-lines/v/3.0.1
Archive: https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz
Integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==
-tslib@2.8.1 | development, optional | 0BSD
+tslib@2.8.1 | development | 0BSD
Lockfile path: node_modules/tslib
Package: https://www.npmjs.com/package/tslib/v/2.8.1
Archive: https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz
Integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
+tunnel@0.0.6 | development | MIT
+ Lockfile path: node_modules/tunnel
+ Package: https://www.npmjs.com/package/tunnel/v/0.0.6
+ Archive: https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz
+ Integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==
tunnel-agent@0.6.0 | runtime, optional | Apache-2.0
Lockfile path: node_modules/tunnel-agent
Package: https://www.npmjs.com/package/tunnel-agent/v/0.6.0
@@ -2650,6 +3265,16 @@ typed-array-buffer@1.0.3 | runtime | MIT
Package: https://www.npmjs.com/package/typed-array-buffer/v/1.0.3
Archive: https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz
Integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==
+typed-inject@5.0.0 | development | Apache-2.0
+ Lockfile path: node_modules/typed-inject
+ Package: https://www.npmjs.com/package/typed-inject/v/5.0.0
+ Archive: https://registry.npmjs.org/typed-inject/-/typed-inject-5.0.0.tgz
+ Integrity: sha512-0Ql2ORqBORLMdAW89TQKZsb1PQkFGImFfVmncXWe7a+AA3+7dh7Se9exxZowH4kbnlvKEFkMxUYdHUpjYWFJaA==
+typed-rest-client@2.3.1 | development | MIT
+ Lockfile path: node_modules/typed-rest-client
+ Package: https://www.npmjs.com/package/typed-rest-client/v/2.3.1
+ Archive: https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-2.3.1.tgz
+ Integrity: sha512-k4kX5Up6qA68D0Cby2AK+6+vM5k3qTxe+/3FqhnHRExjY5cfbOnzjQZbP/LXleF8hVoDvDqxlgk9KK83HoBZlQ==
typescript@7.0.2 | development | Apache-2.0
Lockfile path: node_modules/typescript
Package: https://www.npmjs.com/package/typescript/v/7.0.2
@@ -2660,16 +3285,31 @@ typescript@5.9.3 | development | Apache-2.0
Package: https://www.npmjs.com/package/typescript/v/5.9.3
Archive: https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz
Integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==
+typescript@6.0.3 | development | Apache-2.0
+ Lockfile path: node_modules/typescript6
+ Package: https://www.npmjs.com/package/typescript/v/6.0.3
+ Archive: https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz
+ Integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==
unbash@4.0.12 | development | ISC
Lockfile path: node_modules/unbash
Package: https://www.npmjs.com/package/unbash/v/4.0.12
Archive: https://registry.npmjs.org/unbash/-/unbash-4.0.12.tgz
Integrity: sha512-PnKvRX+Fbxl/usVlTr/31tXIOVAH+8OdC9qaxUQcvurwEnoc3Vk/ipi2Ce8IjMxzK9byfSGdsUBjqqJGc1o5lw==
+underscore@1.13.8 | development | MIT
+ Lockfile path: node_modules/underscore
+ Package: https://www.npmjs.com/package/underscore/v/1.13.8
+ Archive: https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz
+ Integrity: sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==
undici-types@8.9.0 | development | MIT
Lockfile path: node_modules/undici-types
Package: https://www.npmjs.com/package/undici-types/v/8.9.0
Archive: https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz
Integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==
+unicorn-magic@0.3.0 | development | MIT
+ Lockfile path: node_modules/unicorn-magic
+ Package: https://www.npmjs.com/package/unicorn-magic/v/0.3.0
+ Archive: https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz
+ Integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==
unist-util-is@6.0.1 | development | MIT
Lockfile path: node_modules/unist-util-is
Package: https://www.npmjs.com/package/unist-util-is/v/6.0.1
@@ -2695,11 +3335,21 @@ unist-util-visit-parents@6.0.2 | development | MIT
Package: https://www.npmjs.com/package/unist-util-visit-parents/v/6.0.2
Archive: https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz
Integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==
+update-browserslist-db@1.3.4 | development | MIT
+ Lockfile path: node_modules/update-browserslist-db
+ Package: https://www.npmjs.com/package/update-browserslist-db/v/1.3.4
+ Archive: https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.4.tgz
+ Integrity: sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==
util-deprecate@1.0.2 | runtime, optional | MIT
Lockfile path: node_modules/util-deprecate
Package: https://www.npmjs.com/package/util-deprecate/v/1.0.2
Archive: https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz
Integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
+verkit@0.5.0 | development | MIT
+ Lockfile path: node_modules/verkit
+ Package: https://www.npmjs.com/package/verkit/v/0.5.0
+ Archive: https://registry.npmjs.org/verkit/-/verkit-0.5.0.tgz
+ Integrity: sha512-u4J1hr6Y+C/hit34GwL3eIcUf3wngT8TlYUjK7tk0jxydKLAq3sYvifjdTDA6U3KCKRcQCQDGf/srjxfZF3l7g==
vfile@6.0.3 | development | MIT
Lockfile path: node_modules/vfile
Package: https://www.npmjs.com/package/vfile/v/6.0.3
@@ -2880,6 +3530,11 @@ walk-up-path@4.0.0 | development | ISC
Package: https://www.npmjs.com/package/walk-up-path/v/4.0.0
Archive: https://registry.npmjs.org/walk-up-path/-/walk-up-path-4.0.0.tgz
Integrity: sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==
+weapon-regex@2.0.6 | development | Apache-2.0
+ Lockfile path: node_modules/weapon-regex
+ Package: https://www.npmjs.com/package/weapon-regex/v/2.0.6
+ Archive: https://registry.npmjs.org/weapon-regex/-/weapon-regex-2.0.6.tgz
+ Integrity: sha512-iJC2vqYp+bh3r4X6zjuP0h65XqXjgUX3nJgmhj0PQMrdhAbUwCfN76CSTEEQqV3G7468kjOJ69PmytTG8l/8Gg==
which@2.0.2 | runtime | ISC
Lockfile path: node_modules/which
Package: https://www.npmjs.com/package/which/v/2.0.2
@@ -2930,6 +3585,11 @@ yaml@2.9.0 | runtime | ISC
Package: https://www.npmjs.com/package/yaml/v/2.9.0
Archive: https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz
Integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==
+yoctocolors@2.2.0 | development | MIT
+ Lockfile path: node_modules/yoctocolors
+ Package: https://www.npmjs.com/package/yoctocolors/v/2.2.0
+ Archive: https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.2.0.tgz
+ Integrity: sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==
zod@4.4.3 | runtime | MIT
Lockfile path: node_modules/zod
Package: https://www.npmjs.com/package/zod/v/4.4.3
diff --git a/licenses/npm-runtime-notices.txt b/licenses/npm-runtime-notices.txt
index 2a0c45d24..a2032f7a7 100644
--- a/licenses/npm-runtime-notices.txt
+++ b/licenses/npm-runtime-notices.txt
@@ -1,12 +1,12 @@
REA third-party npm runtime copyright, license and NOTICE texts
Collected from integrity-verified locked npm archives. Release-specific supplements and upstream notice gaps are identified in their entries.
External engines are separately obtained; see THIRD_PARTY_NOTICES.md.
-Runtime dependency fingerprint (SHA-256): 63d5b55e027a0cfb3e77989ca337aca905f1652d10cfbfc4d4dd876d3ecd4f3a
+Runtime dependency fingerprint (SHA-256): b0d0151958456c6e72c5720af80d783893be88a7b4c9a409db8505296fe10bd9
-===== @babel/helper-string-parser@8.0.0 =====
+===== @babel/helper-string-parser@8.0.6 =====
Declared license: MIT
-Archive: https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz
-Integrity: sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==
+Archive: https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.6.tgz
+Integrity: sha512-+6HYEBnsDRZHJtUTAEfMQd0zMDqdFHS7C+5vAsEZqBUr3vEMWOWlHJ0Jz1gS8jJIlCvLsuA6ivmeoqzH936YcA==
Scope: npm runtime dependency, including optional platform packages.
--- package/LICENSE ---
@@ -34,11 +34,10 @@ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-===== @babel/helper-validator-identifier@8.0.4 =====
+===== @babel/helper-validator-identifier@8.0.6 =====
Declared license: MIT
-Archive: https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz
-Integrity: sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==
+Archive: https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.6.tgz
+Integrity: sha512-HYx0YmuchRxRSpnjvcNffnPS8DCauKNmnumIuHG3jnQAVewRA7kfJGwF13ayEpSZs7s4R/oBH4eppuXcAQd5JQ==
Scope: npm runtime dependency, including optional platform packages.
--- package/LICENSE ---
@@ -66,7 +65,6 @@ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
===== @babel/parser@8.0.4 =====
Declared license: MIT
Archive: https://registry.npmjs.org/@babel/parser/-/parser-8.0.4.tgz
@@ -8443,5 +8441,3 @@ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
-
-
diff --git a/native/windows/src/authority.hpp b/native/windows/src/authority.hpp
index 13d7e4d60..c8bed30dc 100644
--- a/native/windows/src/authority.hpp
+++ b/native/windows/src/authority.hpp
@@ -123,7 +123,8 @@ napi_value wrap(napi_env env, std::unique_ptr resource);
Resource& resource(napi_env env, napi_value value, Kind kind);
std::unique_ptr openFile(const std::wstring& path, DWORD access = GENERIC_READ,
- bool directory = false);
+ bool directory = false,
+ DWORD finalSharing = FILE_SHARE_READ);
napi_value identity(napi_env env, HANDLE handle, const std::wstring& path);
std::unique_ptr createRuntime(const std::wstring& parent, const std::wstring& prefix);
void closeRuntime(Runtime& runtime);
diff --git a/native/windows/src/filesystem.cc b/native/windows/src/filesystem.cc
index 71c8bbd44..e5129dbb7 100644
--- a/native/windows/src/filesystem.cc
+++ b/native/windows/src/filesystem.cc
@@ -61,7 +61,8 @@ static Handle openComponent(const std::wstring& path, DWORD access, bool directo
return handle;
}
-std::unique_ptr openFile(const std::wstring& requested, DWORD access, bool directory) {
+std::unique_ptr openFile(const std::wstring& requested, DWORD access, bool directory,
+ DWORD finalSharing) {
auto result = std::make_unique(localPath(requested));
result->requestedPath = requested;
const auto& path = result->path;
@@ -77,7 +78,8 @@ std::unique_ptr openFile(const std::wstring& requested, DWORD access, bool
while (offset < path.size()) {
auto end = path.find(L'\\', offset);
const bool final = end == std::wstring::npos;
- auto child = openComponent(path.substr(0, end), final ? access : 0, !final || directory);
+ auto child = openComponent(path.substr(0, end), final ? access : 0, !final || directory,
+ final ? finalSharing : FILE_SHARE_READ);
// Once the next component is pinned against deletion, its parent cannot
// become empty and NTFS cannot convert that parent into a reparse point.
// Relax only ancestor write sharing so unrelated child renames can proceed.
@@ -238,6 +240,100 @@ static void verifyPrivate(HANDLE handle, const std::wstring& path, bool root) {
require(user && system, "Private DACL must allow only current user and SYSTEM", path, ERROR_ACCESS_DENIED);
}
+static std::unique_ptr openPrivateBridgeDirectory(const std::wstring& path) {
+ // The retained runtime root handle carries DELETE authority for owned
+ // cleanup. Windows checks sharing in both directions, so this read-only
+ // inspection handle must share DELETE even though it requests no such right.
+ auto directory = openFile(path, FILE_LIST_DIRECTORY, true,
+ FILE_SHARE_READ | FILE_SHARE_DELETE);
+ verifyPrivate(directory->get(), path, true);
+ return directory;
+}
+
+static Handle openPrivateBridgeDescriptor(HANDLE parent, const std::wstring& name,
+ const std::wstring& path) {
+ require(!name.empty() && name.find_first_of(L"\\/:\"<>|?*") == std::wstring::npos &&
+ name != L"." && name != L".." && name.size() <= 255,
+ "Private descriptor requires one filename component", path, ERROR_INVALID_PARAMETER);
+ UNICODE_STRING coordinate{};
+ coordinate.Buffer = const_cast(name.data());
+ coordinate.Length = static_cast(name.size() * sizeof(wchar_t));
+ coordinate.MaximumLength = coordinate.Length;
+ OBJECT_ATTRIBUTES attributes;
+ InitializeObjectAttributes(&attributes, &coordinate, OBJ_CASE_INSENSITIVE, parent, nullptr);
+ IO_STATUS_BLOCK observation{};
+ HANDLE raw = INVALID_HANDLE_VALUE;
+ // Immutable runtime files retain their original writer handle so later
+ // opens cannot acquire write or delete access. This read-only handle must
+ // share those granted rights for Windows' symmetric share check to pass.
+ const auto status = NtCreateFile(&raw, GENERIC_READ | READ_CONTROL | FILE_READ_ATTRIBUTES | SYNCHRONIZE,
+ &attributes, &observation, nullptr, FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_OPEN,
+ FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_REPARSE_POINT |
+ FILE_NON_DIRECTORY_FILE,
+ nullptr, 0);
+ if (status < 0)
+ throw Failure("Open Cutter bridge descriptor relative to its verified directory failed", path,
+ RtlNtStatusToDosError(status));
+ Handle descriptor(raw);
+ require(descriptor.valid() && observation.Information == FILE_OPENED,
+ "Cutter bridge descriptor was not opened as an existing file", path, ERROR_ACCESS_DENIED);
+ FILE_STANDARD_INFO standard{};
+ require(GetFileInformationByHandleEx(descriptor.get(), FileStandardInfo, &standard, sizeof(standard)),
+ "Read Cutter bridge descriptor metadata failed", path);
+ require(!standard.Directory && standard.EndOfFile.QuadPart >= 0 &&
+ standard.EndOfFile.QuadPart <= 64 * 1024,
+ "Cutter bridge descriptor exceeds its byte limit or is not a file", path, ERROR_FILE_TOO_LARGE);
+ FILE_ATTRIBUTE_TAG_INFO fileAttributes{};
+ require(GetFileInformationByHandleEx(descriptor.get(), FileAttributeTagInfo, &fileAttributes, sizeof(fileAttributes)),
+ "Read Cutter bridge descriptor attributes failed", path);
+ require((fileAttributes.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0,
+ "Cutter bridge descriptor cannot be a reparse point", path, ERROR_REPARSE_TAG_INVALID);
+ verifyPrivate(descriptor.get(), path, false);
+ return descriptor;
+}
+
+static napi_value verifyCutterBridgeDirectory(napi_env env, const std::vector& args) {
+ require(args.size() == 1, "Cutter bridge directory verification requires one path", L"",
+ ERROR_INVALID_PARAMETER);
+ const auto path = wide(env, args[0]);
+ const auto directory = openPrivateBridgeDirectory(path);
+ auto result = object(env);
+ set(env, result, "privateDacl", boolean(env, true));
+ set(env, result, "identity", identity(env, directory->get(), path));
+ return result;
+}
+
+static napi_value readCutterBridgeDescriptor(napi_env env, const std::vector& args) {
+ require(args.size() == 3, "Cutter bridge descriptor read requires directory, name, and byte limit", L"",
+ ERROR_INVALID_PARAMETER);
+ const auto path = wide(env, args[0]);
+ const auto name = wide(env, args[1]);
+ const double maximum = numeric(env, args[2]);
+ require(maximum >= 1 && maximum <= 64 * 1024 && maximum == static_cast(maximum),
+ "Cutter bridge descriptor limit is outside the supported range", path, ERROR_INVALID_PARAMETER);
+ auto directory = openPrivateBridgeDirectory(path);
+ auto descriptor = openPrivateBridgeDescriptor(directory->get(), name, path + L"\\" + name);
+ FILE_STANDARD_INFO standard{};
+ require(GetFileInformationByHandleEx(descriptor.get(), FileStandardInfo, &standard, sizeof(standard)),
+ "Read Cutter bridge descriptor size failed", path);
+ require(standard.EndOfFile.QuadPart <= maximum,
+ "Cutter bridge descriptor exceeds the requested byte limit", path, ERROR_FILE_TOO_LARGE);
+ std::vector bytes(static_cast(standard.EndOfFile.QuadPart));
+ size_t offset = 0;
+ while (offset < bytes.size()) {
+ DWORD count = 0;
+ const DWORD requested = static_cast(std::min(bytes.size() - offset, MAXDWORD));
+ require(ReadFile(descriptor.get(), bytes.data() + offset, requested, &count, nullptr) && count > 0,
+ "Read Cutter bridge descriptor through its verified handle failed", path);
+ offset += count;
+ }
+ napi_value result;
+ const void* source = bytes.empty() ? static_cast("") : bytes.data();
+ check(napi_create_buffer_copy(env, bytes.size(), source, nullptr, &result));
+ return result;
+}
+
std::unique_ptr createRuntime(const std::wstring& parentPath, const std::wstring& prefix) {
std::unique_ptr parent;
try { parent = openFile(parentPath, 0, true); }
@@ -559,6 +655,8 @@ static napi_value snapshot(napi_env env, napi_value rootValue, Runtime& root,
}
napi_value filesystemCall(napi_env env, const std::wstring& operation, const std::vector& args) {
+ if (operation == L"cutter_bridge_verify_directory") return verifyCutterBridgeDirectory(env, args);
+ if (operation == L"cutter_bridge_read_descriptor") return readCutterBridgeDescriptor(env, args);
const auto expected = operation == L"open" || operation == L"close" || operation == L"runtime_close" || operation == L"runtime_snapshot_cancel" ? 1
: operation == L"runtime_create" || operation == L"runtime_mkdir" || operation == L"runtime_open" ? 2 : 3;
require(args.size() == static_cast(expected), "Wrong native filesystem argument count", L"", ERROR_INVALID_PARAMETER);
diff --git a/native/windows/src/process.cc b/native/windows/src/process.cc
index 80a4d0ac5..42ee9582b 100644
--- a/native/windows/src/process.cc
+++ b/native/windows/src/process.cc
@@ -464,6 +464,7 @@ napi_value inspect(napi_env env, std::unique_ptr& probe) {
set(env, result, "architecture", string(env, std::string("x64")));
set(env, result, "filesystem", filesystem);
set(env, result, "privateDacl", boolean(env, true));
+ set(env, result, "privateBridgeDescriptorAccess", boolean(env, true));
set(env, result, "atomicJobAssignment", boolean(env, true));
set(env, result, "killOnOwnerClose", boolean(env, true));
set(env, result, "protocolStdin", boolean(env, true));
diff --git a/package-lock.json b/package-lock.json
index 7eae3799e..48764a796 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -54,6 +54,9 @@
},
"devDependencies": {
"@fast-check/vitest": "0.5.0",
+ "@stryker-mutator/core": "10.0.0",
+ "@stryker-mutator/typescript-checker": "10.0.0",
+ "@stryker-mutator/vitest-runner": "10.0.0",
"@types/cross-spawn": "6.0.6",
"@types/node": "26.6.4",
"@types/semver": "7.7.1",
@@ -70,6 +73,7 @@
"turbo": "2.11.7",
"typescript": "7.0.2",
"typescript-termux": "npm:typescript@5.9.3",
+ "typescript6": "npm:typescript@6.0.3",
"vitepress": "1.6.4",
"vitest": "5.0.3"
},
@@ -338,24 +342,390 @@
"node": ">= 14.0.0"
}
},
- "node_modules/@babel/helper-string-parser": {
+ "node_modules/@babel/code-frame": {
+ "version": "8.0.6",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-8.0.6.tgz",
+ "integrity": "sha512-uMHhvmDmwFiLuiozaoRYTZHqMFuu3fem/X9JY78lohAuLoh8LSGGC8JqUzo2cVuBsBxkjMZNk56CE7ock8Xxkw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^8.0.6",
+ "js-tokens": "^10.0.0"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "8.0.5",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-8.0.5.tgz",
+ "integrity": "sha512-YLsYoQMvL8l8WrGpN3Zj7O1wK5LEBN+cQtux7BcuHyxIXve724XG+zuJ1n3U1cUweRtTzQOA4IHbuQw3N34SZw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "8.0.7",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-8.0.7.tgz",
+ "integrity": "sha512-ZIsZWBG6Y1oa+8fkQVp0xeCdjCHBkXFCCIWrmg9SvejVvfcVQgKKRmGrBDWfM7rilhvnRoEuaqa5dkK7KB3pHA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^8.0.6",
+ "@babel/generator": "^8.0.6",
+ "@babel/helper-compilation-targets": "^8.0.7",
+ "@babel/helpers": "^8.0.7",
+ "@babel/parser": "^8.0.7",
+ "@babel/template": "^8.0.0",
+ "@babel/traverse": "^8.0.7",
+ "@babel/types": "^8.0.6",
+ "@types/gensync": "^1.0.5",
+ "convert-source-map": "^2.0.0",
+ "empathic": "^2.0.1",
+ "gensync": "^1.0.0-beta.2",
+ "import-meta-resolve": "^4.2.0",
+ "json5": "^2.2.3",
+ "obug": "^3.0.0",
+ "verkit": "^0.5.0"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/core/node_modules/@babel/parser": {
+ "version": "8.0.7",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.7.tgz",
+ "integrity": "sha512-UkDwEbPO1px2JQSrdh4qIgaH1JgG7GO/pFeJkpxhDxIETkT/QvGpnigXIw75ViHYPtIgKITmVUJqLjiO+JuWjA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^8.0.6"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/core/node_modules/@babel/types": {
+ "version": "8.0.6",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.6.tgz",
+ "integrity": "sha512-c+xgSWboV2pdwXP67BUWDVRHi2BO5Q3KwxyFVs7taVTY+fsK47/L51ZXQoV9rozHJDNHvJ4v4WEsI/IlxV2l2A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^8.0.6",
+ "@babel/helper-validator-identifier": "^8.0.6"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/core/node_modules/obug": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/obug/-/obug-3.0.0.tgz",
+ "integrity": "sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/sxzz",
+ "https://opencollective.com/debug"
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "8.0.6",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-8.0.6.tgz",
+ "integrity": "sha512-/pcanjdRsonCexCuxgGD8fKnBLLzk2i9uM4kmcmGyyev2R5YDs+rafMYZ89RvaGY1mW8T7C5au0TyYaEeHBGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^8.0.6",
+ "@babel/types": "^8.0.6",
+ "@jridgewell/gen-mapping": "0.4.0-beta.0",
+ "@jridgewell/trace-mapping": "^0.3.31",
+ "@types/jsesc": "^2.5.0",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/generator/node_modules/@babel/parser": {
+ "version": "8.0.7",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.7.tgz",
+ "integrity": "sha512-UkDwEbPO1px2JQSrdh4qIgaH1JgG7GO/pFeJkpxhDxIETkT/QvGpnigXIw75ViHYPtIgKITmVUJqLjiO+JuWjA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^8.0.6"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/generator/node_modules/@babel/types": {
+ "version": "8.0.6",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.6.tgz",
+ "integrity": "sha512-c+xgSWboV2pdwXP67BUWDVRHi2BO5Q3KwxyFVs7taVTY+fsK47/L51ZXQoV9rozHJDNHvJ4v4WEsI/IlxV2l2A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^8.0.6",
+ "@babel/helper-validator-identifier": "^8.0.6"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/helper-annotate-as-pure": {
"version": "8.0.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz",
- "integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==",
+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-8.0.0.tgz",
+ "integrity": "sha512-NSpMkMsvvZqzThJ0p1B02cbtA2ObEyfBvq950bmNkyxsxvcxwhvvCB036rKhlEnuBBo30bOrk13u3FzlKSoRrw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^8.0.0"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "8.0.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-8.0.7.tgz",
+ "integrity": "sha512-ilAZCdOOlOVA7dQbaFNw2f/59p6GW33NxTopc3DvEE3efp3ZXTrGX/yZScaKw8/vd9bq31Ag0EcXiLw32bAgJg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^8.0.5",
+ "@babel/helper-validator-option": "^8.0.0",
+ "browserslist": "^4.24.0",
+ "flru": "1.0.2",
+ "verkit": "^0.5.0"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/helper-create-class-features-plugin": {
+ "version": "8.0.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-8.0.7.tgz",
+ "integrity": "sha512-7ug3KrOhNVPPxFFLTel0/pMzimKzU/vTlNJKVL/dKguCpmNJZjjibyFbWg+Qs8QHmiIZPNuZ2UNnDGdHWGEpOg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^8.0.0",
+ "@babel/helper-member-expression-to-functions": "^8.0.5",
+ "@babel/helper-optimise-call-expression": "^8.0.0",
+ "@babel/helper-replace-supers": "^8.0.1",
+ "@babel/helper-skip-transparent-expression-wrappers": "^8.0.0",
+ "@babel/traverse": "^8.0.7",
+ "verkit": "^0.5.0"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^8.0.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "8.0.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-8.0.6.tgz",
+ "integrity": "sha512-OdnA/N52LhzFpeUuVz5YGuSWjc7TPHiFmVYZbPlSgExocO2GwbxVpSni9MVs5wwW+wZ4kPuwBEBN9pIPDYz1mA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/helper-member-expression-to-functions": {
+ "version": "8.0.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-8.0.5.tgz",
+ "integrity": "sha512-GLe05QD98BkNFTkjaqeqF9QSRoHLKrrB4tpoplyQuuPrQJ72rtmkM4wvqJLX/sjZPqkbK5MUYSsuUTqdJoOVjA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^8.0.5",
+ "@babel/types": "^8.0.5"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/helper-member-expression-to-functions/node_modules/@babel/types": {
+ "version": "8.0.6",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.6.tgz",
+ "integrity": "sha512-c+xgSWboV2pdwXP67BUWDVRHi2BO5Q3KwxyFVs7taVTY+fsK47/L51ZXQoV9rozHJDNHvJ4v4WEsI/IlxV2l2A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^8.0.6",
+ "@babel/helper-validator-identifier": "^8.0.6"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-8.0.0.tgz",
+ "integrity": "sha512-NZ7mSS93o4ndX4KrbD7W8Sf3QT8Qe24PrnFyUcuOPDzK6faqDFKjY9RG7he7+I7FdiQ4llpnosFqzrXa+Vy3Ew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^8.0.0",
+ "@babel/types": "^8.0.0"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "8.0.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-8.0.6.tgz",
+ "integrity": "sha512-YUi+3X0cb0v97xKhuqkLdrwQtP6XXv1eVbhLRCFyMwG481UskbMQk4mjH7K/Jg3oJ1KLskvFwwq4kF/QnABkWQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^8.0.0",
+ "@babel/helper-validator-identifier": "^8.0.6",
+ "@babel/traverse": "^8.0.6"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^8.0.0"
+ }
+ },
+ "node_modules/@babel/helper-optimise-call-expression": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-8.0.0.tgz",
+ "integrity": "sha512-3W6satvtPuCUkUx63S2jMoW9EQNYkADgs1HTfufmL7gCmAulHMKupA/12WNz4A0GMMFn/YnWWwqOT9IZrJHQjg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^8.0.0"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-8.0.1.tgz",
+ "integrity": "sha512-3PKFgjTyPlhFhorfP+SjKQxLViIL++zWjFOO4hGriYU+Bsm983DxEM1JmDRJVWXV0O9npu+xXRqz7Pbd3mh70g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^8.0.0"
+ }
+ },
+ "node_modules/@babel/helper-replace-supers": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-8.0.1.tgz",
+ "integrity": "sha512-B1SZADIcy3tmH8CmWvj4SHi/oAPom4UL3uknTc2QRNsPVLFk/sPnZvQL/8kj7Y5omvjMqie0vklvs6XM4OLW5Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-member-expression-to-functions": "^8.0.0",
+ "@babel/helper-optimise-call-expression": "^8.0.0",
+ "@babel/traverse": "^8.0.0"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^8.0.0"
+ }
+ },
+ "node_modules/@babel/helper-skip-transparent-expression-wrappers": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-8.0.0.tgz",
+ "integrity": "sha512-xmCA9kP3IhySsqhzwIdWGlDN/1A4cCKNBO/uwZx/3YzmDoMePwno2Q5/Bq0q+tYaKbeF940YiKV/kaW8Mzvpjw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^8.0.0",
+ "@babel/types": "^8.0.0"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "8.0.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.6.tgz",
+ "integrity": "sha512-+6HYEBnsDRZHJtUTAEfMQd0zMDqdFHS7C+5vAsEZqBUr3vEMWOWlHJ0Jz1gS8jJIlCvLsuA6ivmeoqzH936YcA==",
"license": "MIT",
"engines": {
"node": "^22.18.0 || >=24.11.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
- "version": "8.0.4",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz",
- "integrity": "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==",
+ "version": "8.0.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.6.tgz",
+ "integrity": "sha512-HYx0YmuchRxRSpnjvcNffnPS8DCauKNmnumIuHG3jnQAVewRA7kfJGwF13ayEpSZs7s4R/oBH4eppuXcAQd5JQ==",
"license": "MIT",
"engines": {
"node": "^22.18.0 || >=24.11.0"
}
},
+ "node_modules/@babel/helper-validator-option": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-8.0.0.tgz",
+ "integrity": "sha512-U4Dybxh4WESWHt5XhBeExi4DrY0/DNK1aHpQbsrQXCUbFHuMweT0TpLEWKvaraV2Y6fS+ZXunsZ8zIuZIgvF2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "8.0.7",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-8.0.7.tgz",
+ "integrity": "sha512-5dMj5kFwS2AY7Dt4PZp4px3c5ThMbbEa7tZfuHTncCGSFYq8uoA1ufDE1IS/lYpQedE+3+GQ3cPZUiplIu195w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^8.0.0",
+ "@babel/types": "^8.0.6"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/helpers/node_modules/@babel/types": {
+ "version": "8.0.6",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.6.tgz",
+ "integrity": "sha512-c+xgSWboV2pdwXP67BUWDVRHi2BO5Q3KwxyFVs7taVTY+fsK47/L51ZXQoV9rozHJDNHvJ4v4WEsI/IlxV2l2A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^8.0.6",
+ "@babel/helper-validator-identifier": "^8.0.6"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
"node_modules/@babel/parser": {
"version": "8.0.4",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.4.tgz",
@@ -371,6 +741,329 @@
"node": "^22.18.0 || >=24.11.0"
}
},
+ "node_modules/@babel/plugin-proposal-decorators": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-8.0.2.tgz",
+ "integrity": "sha512-+C6O6KKXU7BBq1GNaIkFJxrALUVGRcr+WeWm4OcuRl3h+l/CmNfcTLMrT2Lm3uvGBimBH/8pEBRrXJFLoO67Gg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-class-features-plugin": "^8.0.1",
+ "@babel/helper-plugin-utils": "^8.0.1",
+ "@babel/plugin-syntax-decorators": "^8.0.1"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^8.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-decorators": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-8.0.1.tgz",
+ "integrity": "sha512-NI+0S/6MvR6GlcQFwjDZ+WIc2qvG6TXN534lYs9llNldwW4b7Dh6KTtk030FA0xWdYGs4t1lWo+OEWN8wGB+Nw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^8.0.1"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^8.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-jsx": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-8.0.1.tgz",
+ "integrity": "sha512-n0jtCOxEovhU7METqSQjcZO9pX53nu9uNIjMS+hEt+Nt9jA7oOZoBIgbCxhhASmF6T6rPDGge5UAvh6Z4eFz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^8.0.1"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^8.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-typescript": {
+ "version": "8.0.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-8.0.3.tgz",
+ "integrity": "sha512-jmTPwps7oSQSZaV1SxkQ3C12UWyufGysGc5OzDpZzvPAIX4mO7dJT3hoqkWVrSImvkcMiknir1iLN1SNV/CZzg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^8.0.1"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^8.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-destructuring": {
+ "version": "8.0.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-8.0.5.tgz",
+ "integrity": "sha512-evjxc5fGvpXG2WMGSalpX8IJZVumLIDgk5r5eRhvXRDqFX/GPWq1BeRfJalPCcwJfrR5A/wvegUhx9W5Nr7/mg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^8.0.1"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^8.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-explicit-resource-management": {
+ "version": "8.0.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-8.0.5.tgz",
+ "integrity": "sha512-Vr20jk/ZxRGu30o0jYvcKl2Pf0tiyr8H9q2ZGxYD6yjOpv9DdP6//I85q0cSOdADZhsu/ZZoApjGVCfl4xnVJw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^8.0.1",
+ "@babel/plugin-transform-destructuring": "^8.0.5"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^8.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-modules-commonjs": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-8.0.1.tgz",
+ "integrity": "sha512-PMuzulWrrzFNmY3lXSk/tV9NRb7y0eZZLJY4UEo2TKszroxvUZHAPPi+T9FDyrQhod+TQA+t+8/QYaaMpiEuhA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-transforms": "^8.0.1",
+ "@babel/helper-plugin-utils": "^8.0.1"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^8.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-display-name": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-8.0.1.tgz",
+ "integrity": "sha512-soLishXlkyu6jcICPyO3HEP7A3GCzKEnn7XfvYrImuWEOwFAz93qShmWSYPf5ww0ZkO4By0zsN2bVIDF54fSdA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^8.0.1"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^8.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-8.0.1.tgz",
+ "integrity": "sha512-NgkoF7Uq+30TmOPDdNUimT0Nta02uVjqJRFNlVWKrbOCu/CkzfHa4aMnIs0lMpkMmZmWA1e42Va+F04i/pY1zw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^8.0.0",
+ "@babel/helper-module-imports": "^8.0.0",
+ "@babel/helper-plugin-utils": "^8.0.1",
+ "@babel/plugin-syntax-jsx": "^8.0.1",
+ "@babel/types": "^8.0.0"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^8.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-development": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-8.0.1.tgz",
+ "integrity": "sha512-Hb+HUZpV9KFHjm+F+P3aLDMi8QXU9l3ROCQv20z18Me2sGyW5nNNR5YTevNlgHvCpFek3BnAwhDGq/BRndXViw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/plugin-transform-react-jsx": "^8.0.1"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^8.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-pure-annotations": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-8.0.1.tgz",
+ "integrity": "sha512-7/8UwU8hoPBurXa9tUiTTC8aACTRy5tCqLUtqikHp2eGiWoEB57AduOdbQ71OOMTEvawKrGhv3WfzkDpI+/oSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^8.0.0",
+ "@babel/helper-plugin-utils": "^8.0.1"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^8.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-typescript": {
+ "version": "8.0.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-8.0.6.tgz",
+ "integrity": "sha512-WF3yRlQtClzScvQbWct00ix63+w3DPiyU0WmCBU4YqK539n3VvJFElElpevoD8wfu6953CbiNoUZBi2B7/5jXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^8.0.0",
+ "@babel/helper-create-class-features-plugin": "^8.0.6",
+ "@babel/helper-plugin-utils": "^8.0.1",
+ "@babel/helper-skip-transparent-expression-wrappers": "^8.0.0",
+ "@babel/plugin-syntax-typescript": "^8.0.3"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^8.0.0"
+ }
+ },
+ "node_modules/@babel/preset-react": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-8.0.1.tgz",
+ "integrity": "sha512-jrFuPp/pTddFZbtmWhdLNAYc6UMcpboeUPnw0BBrm4nOmcAko/1TRcFi1PzWCeOFRU+VaSiKmat87W1HvR7mIg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^8.0.1",
+ "@babel/helper-validator-option": "^8.0.0",
+ "@babel/plugin-transform-react-display-name": "^8.0.1",
+ "@babel/plugin-transform-react-jsx": "^8.0.1",
+ "@babel/plugin-transform-react-jsx-development": "^8.0.1",
+ "@babel/plugin-transform-react-pure-annotations": "^8.0.1"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^8.0.0"
+ }
+ },
+ "node_modules/@babel/preset-typescript": {
+ "version": "8.0.7",
+ "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-8.0.7.tgz",
+ "integrity": "sha512-sUFLFphAG6YNjCnJpSKpyoNxkfyeM2KNuUjJJN0R6dZusMcK2V3Fg11MVHPFvb37AZeDNocc+xM748vdh9z82Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^8.0.1",
+ "@babel/helper-validator-option": "^8.0.0",
+ "@babel/plugin-transform-modules-commonjs": "^8.0.1",
+ "@babel/plugin-transform-typescript": "^8.0.6"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^8.0.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-8.0.0.tgz",
+ "integrity": "sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^8.0.0",
+ "@babel/parser": "^8.0.0",
+ "@babel/types": "^8.0.0"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "8.0.7",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-8.0.7.tgz",
+ "integrity": "sha512-bVV0brinsTWgc6ptSIoQo3GbITtl0VZAqEUJbHHcyfDPEu+q/fEvlYEa+6H85HoK8IdksKBFtKJbJFZ6Kff+zg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^8.0.6",
+ "@babel/generator": "^8.0.6",
+ "@babel/helper-globals": "^8.0.6",
+ "@babel/parser": "^8.0.7",
+ "@babel/template": "^8.0.0",
+ "@babel/types": "^8.0.6",
+ "obug": "^3.0.0"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/traverse/node_modules/@babel/parser": {
+ "version": "8.0.7",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.7.tgz",
+ "integrity": "sha512-UkDwEbPO1px2JQSrdh4qIgaH1JgG7GO/pFeJkpxhDxIETkT/QvGpnigXIw75ViHYPtIgKITmVUJqLjiO+JuWjA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^8.0.6"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/traverse/node_modules/@babel/types": {
+ "version": "8.0.6",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.6.tgz",
+ "integrity": "sha512-c+xgSWboV2pdwXP67BUWDVRHi2BO5Q3KwxyFVs7taVTY+fsK47/L51ZXQoV9rozHJDNHvJ4v4WEsI/IlxV2l2A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^8.0.6",
+ "@babel/helper-validator-identifier": "^8.0.6"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/traverse/node_modules/obug": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/obug/-/obug-3.0.0.tgz",
+ "integrity": "sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/sxzz",
+ "https://opencollective.com/debug"
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
"node_modules/@babel/types": {
"version": "8.0.4",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.4.tgz",
@@ -579,6 +1272,361 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@inquirer/ansi": {
+ "version": "2.0.9",
+ "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.9.tgz",
+ "integrity": "sha512-0Ywt3S8tqM6TWUcsRlBFQ1x+X3/o8kktX0ZiKAnByYb9GFDknnOhse1PVtfiD2BXPI8ar+BWx9PV9R0lQIcygw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
+ }
+ },
+ "node_modules/@inquirer/checkbox": {
+ "version": "5.2.6",
+ "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-5.2.6.tgz",
+ "integrity": "sha512-XkOardciF3tow2NUlY6/Qjk1QqYa+6WpmVLTpxXl97Uv4erczDEZ+xg1j6LqBf0ekcRSUiICQA4nQXHHkVKQ9w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/ansi": "^2.0.9",
+ "@inquirer/core": "^12.0.4",
+ "@inquirer/figures": "^2.0.9",
+ "@inquirer/type": "4.2.0"
+ },
+ "engines": {
+ "node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/confirm": {
+ "version": "6.3.3",
+ "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.3.3.tgz",
+ "integrity": "sha512-ImOBgpl/sJTFp8naeSkNu4S0Sf8F8pxv3/CUx+LoBAdQvCVqFz6rSB2A/oZYsJge3+yphbEDxEkfaRNYq1OU0A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/core": "^12.0.4",
+ "@inquirer/type": "4.2.0"
+ },
+ "engines": {
+ "node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/core": {
+ "version": "12.0.4",
+ "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-12.0.4.tgz",
+ "integrity": "sha512-M5fB67y7jPObI45vhD82RskWwCSm0scOvenV/I6K8tIi2gfr9DXqQqJ+iw+Wi6SgapikdOiJibYzz1gW47fsDg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/ansi": "^2.0.9",
+ "@inquirer/figures": "^2.0.9",
+ "@inquirer/type": "4.2.0",
+ "cli-width": "^4.1.0",
+ "fast-wrap-ansi": "^0.2.0",
+ "mute-stream": "^3.0.0",
+ "signal-exit": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/editor": {
+ "version": "5.3.4",
+ "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-5.3.4.tgz",
+ "integrity": "sha512-imzxQDfw26NpcJAubMj/dE5RjU9Mp80IcT+FzsD6YoxXmL8L1QYnAFdCNv7ccGWh//73+0YP+1kfhwnExdl2VQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/core": "^12.0.4",
+ "@inquirer/external-editor": "^3.0.6",
+ "@inquirer/type": "4.2.0"
+ },
+ "engines": {
+ "node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/expand": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-5.1.6.tgz",
+ "integrity": "sha512-JfiLKi7qGuHmcQO/DqR10UFEp054Y1FXZaszP5ZEfHhgHeuFoT5nNFQIXFhed/5/p07SbsV2tIJFwG/dg5+NmQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/core": "^12.0.4",
+ "@inquirer/type": "4.2.0"
+ },
+ "engines": {
+ "node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/external-editor": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-3.0.6.tgz",
+ "integrity": "sha512-rWmw6WG8JZ7LMn/srXEylYGhL6EXXJeROE7/4x4GREh6NgzqJBDbIgpNGsYoLPfobeb4VZtEYzPGnT5EFNYhiQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chardet": "^2.1.1",
+ "iconv-lite": "^0.7.2"
+ },
+ "engines": {
+ "node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/figures": {
+ "version": "2.0.9",
+ "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.9.tgz",
+ "integrity": "sha512-EAWgUTGQ/Umgga51dE3B2PUHbufuXarDfg86uVgoSgNHNNQnyFKcOrQLWVqYMghuSyHh8+2HUH0Js9cTC1WAdg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
+ }
+ },
+ "node_modules/@inquirer/input": {
+ "version": "5.1.7",
+ "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-5.1.7.tgz",
+ "integrity": "sha512-Icrs9tuMBCxGI+lFYNO71VFOnqGO8IIrLIGqnTGh07X8jZ2la9QLAYxXcikHZQjjVhIZUtU2xyH89DVN/pPhVg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/core": "^12.0.4",
+ "@inquirer/type": "4.2.0"
+ },
+ "engines": {
+ "node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/number": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-4.2.4.tgz",
+ "integrity": "sha512-C6g80U4U2+w2Vxa1CB0MN3a2WnQIfzLSUG0lduO3Au9GsirJsKATZkOwZPK+vZ1BlAlz+/Z5ParfQCS9E0svmg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/core": "^12.0.4",
+ "@inquirer/type": "4.2.0"
+ },
+ "engines": {
+ "node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/password": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-5.2.3.tgz",
+ "integrity": "sha512-1Uo2qfI2K3wXbfunMpvwAzoZPIUf5iEETwDGqRVbswoorXGqYQJ/bfIJFeLj+pxjq+e2UFPNOmx+fsIl4Urigw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/ansi": "^2.0.9",
+ "@inquirer/core": "^12.0.4",
+ "@inquirer/type": "4.2.0"
+ },
+ "engines": {
+ "node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/prompts": {
+ "version": "8.7.3",
+ "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-8.7.3.tgz",
+ "integrity": "sha512-DHK20kO+H5k/XcJQpHQUpVQbMYHHhZchlGWhZVMUV4WIuRuBW5744hipOtriE1vMqPLgnIyXFMHWKkIbTepa4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/checkbox": "^5.2.6",
+ "@inquirer/confirm": "^6.3.3",
+ "@inquirer/editor": "^5.3.4",
+ "@inquirer/expand": "^5.1.6",
+ "@inquirer/input": "^5.1.7",
+ "@inquirer/number": "^4.2.4",
+ "@inquirer/password": "^5.2.3",
+ "@inquirer/rawlist": "^5.3.6",
+ "@inquirer/search": "^4.3.4",
+ "@inquirer/select": "^5.2.6"
+ },
+ "engines": {
+ "node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/rawlist": {
+ "version": "5.3.6",
+ "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-5.3.6.tgz",
+ "integrity": "sha512-2exExhQ5xTBqfN/rRlZkvM1Gm3b0eiLcWe2NqxWxe7qYmnU4M3j/n9TwzfPUb+Q7dAAz9TmBtDWWdF3jbp4ZLA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/core": "^12.0.4",
+ "@inquirer/type": "4.2.0"
+ },
+ "engines": {
+ "node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/search": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-4.3.4.tgz",
+ "integrity": "sha512-xsk0UeFP04jsbEeFMzghjUaH2aHXyt+WykSajQzq/8qyBNHsgGYHea/sFwv5T08kW7Sp+KDX8aF124zPUIs1Ww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/core": "^12.0.4",
+ "@inquirer/figures": "^2.0.9",
+ "@inquirer/type": "4.2.0"
+ },
+ "engines": {
+ "node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/select": {
+ "version": "5.2.6",
+ "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-5.2.6.tgz",
+ "integrity": "sha512-jFu7CFghlr0vpi/326a4wz6BGkJH9X3H/iIu+ahyV2Ja52ktqQE3fC9BYP47JKeSPQYGUv5xquE2Vp9PbepZ3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@inquirer/ansi": "^2.0.9",
+ "@inquirer/core": "^12.0.4",
+ "@inquirer/figures": "^2.0.9",
+ "@inquirer/type": "4.2.0"
+ },
+ "engines": {
+ "node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@inquirer/type": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.2.0.tgz",
+ "integrity": "sha512-2rnHX8RPo4glJYqnFc/v3nQeIhDcjyRRyaT7MGcfL/8xv/8K03SXYoA2tX06WVSKywWOE/ncbG3dtuzLjR5TCQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
+ },
+ "peerDependencies": {
+ "@types/node": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.4.0-beta.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.4.0-beta.0.tgz",
+ "integrity": "sha512-JdGNkbE4GlNPYQhM0L95fBQr7ctLZJ276QXQLTad4t1oSdnnCI3fDq9DW3BqYAWv8Wc3+HS+4Gsii1oPMCfz1w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.6.0-beta.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
@@ -2655,6 +3703,13 @@
"node": ">=22"
}
},
+ "node_modules/@sec-ant/readable-stream": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz",
+ "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@shikijs/core": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-2.5.0.tgz",
@@ -2742,6 +3797,148 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@sindresorhus/merge-streams": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz",
+ "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@stryker-mutator/api": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/@stryker-mutator/api/-/api-10.0.0.tgz",
+ "integrity": "sha512-ZtAJ0ZT3MVRCWJTBE2h90XB/6E+4lifHYtcTyNG6nU2nLekPgTo4gD5esjX6Okxo1b/JB4jJzyxYB54fwKAoJw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "mutation-testing-metrics": "3.8.4",
+ "mutation-testing-report-schema": "3.8.4",
+ "tslib": "~2.8.0",
+ "typed-inject": "~5.0.0"
+ },
+ "engines": {
+ "node": ">=22.0.0"
+ }
+ },
+ "node_modules/@stryker-mutator/core": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/@stryker-mutator/core/-/core-10.0.0.tgz",
+ "integrity": "sha512-ZvMsRyaXQQ5e6Thcid9pkuODv6Fn9E3nrBQJUap+hcJuGJ4unm26afo3m6YKSjn8kinyxJ/3TXf0cTWRDaTxVw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@inquirer/prompts": "^8.0.0",
+ "@stryker-mutator/api": "10.0.0",
+ "@stryker-mutator/instrumenter": "10.0.0",
+ "@stryker-mutator/util": "10.0.0",
+ "ajv": "~8.20.0",
+ "chalk": "~5.6.0",
+ "commander": "~14.0.0",
+ "diff-match-patch": "1.0.5",
+ "emoji-regex": "~10.6.0",
+ "execa": "~9.6.0",
+ "json-rpc-2.0": "^1.7.0",
+ "lodash.groupby": "~4.6.0",
+ "minimatch": "~10.2.4",
+ "mutation-server-protocol": "~0.4.0",
+ "mutation-testing-elements": "3.8.4",
+ "mutation-testing-metrics": "3.8.4",
+ "mutation-testing-report-schema": "3.8.4",
+ "npm-run-path": "~6.0.0",
+ "progress": "~2.0.3",
+ "rxjs": "~7.8.1",
+ "semver": "^7.6.3",
+ "source-map": "~0.7.4",
+ "tree-kill": "~1.2.2",
+ "tslib": "2.8.1",
+ "typed-inject": "~5.0.0",
+ "typed-rest-client": "~2.3.0"
+ },
+ "bin": {
+ "stryker": "bin/stryker.js"
+ },
+ "engines": {
+ "node": ">=22.0.0"
+ }
+ },
+ "node_modules/@stryker-mutator/instrumenter": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/@stryker-mutator/instrumenter/-/instrumenter-10.0.0.tgz",
+ "integrity": "sha512-B7Wmn1KlEWyFeOz6D6oGvQGRfi5Xw3VemG6dEKvFQp4qLvxD9Mf4kcZghfxffgnYwXd3bFgqXsJ+ZGlhdfIOrQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@babel/core": "~8.0.0",
+ "@babel/generator": "~8.0.0",
+ "@babel/parser": "~8.0.0",
+ "@babel/plugin-proposal-decorators": "~8.0.0",
+ "@babel/plugin-transform-explicit-resource-management": "^8.0.0",
+ "@babel/preset-react": "~8.0.0",
+ "@babel/preset-typescript": "~8.0.0",
+ "@babel/traverse": "~8.0.4",
+ "@stryker-mutator/api": "10.0.0",
+ "@stryker-mutator/util": "10.0.0",
+ "angular-html-parser": "~10.11.0",
+ "semver": "~7.8.0",
+ "tslib": "2.8.1",
+ "weapon-regex": "~2.0.0"
+ },
+ "engines": {
+ "node": ">=22.0.0"
+ }
+ },
+ "node_modules/@stryker-mutator/typescript-checker": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/@stryker-mutator/typescript-checker/-/typescript-checker-10.0.0.tgz",
+ "integrity": "sha512-GLvC0LVd8RJ5SxR3eFcgb7DFKmw07DYtPXs942z6Qh7l6p7YBnFyd7lPw7nvA9UKEYDd22embrFmLlyC1XJjXw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@stryker-mutator/api": "10.0.0",
+ "@stryker-mutator/util": "10.0.0",
+ "semver": "~7.8.0"
+ },
+ "engines": {
+ "node": ">=22.0.0"
+ },
+ "peerDependencies": {
+ "@stryker-mutator/core": "10.0.0",
+ "typescript": ">=3.6"
+ }
+ },
+ "node_modules/@stryker-mutator/util": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/@stryker-mutator/util/-/util-10.0.0.tgz",
+ "integrity": "sha512-LzOpHiJaCp2ABQgnPMlrQQcsK43bd5Vo/2FGL78aN62yDoeRQ+4j3tzeuXxK5OAHdC3fUz6TDoy4IsoAuLAd3w==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/@stryker-mutator/vitest-runner": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/@stryker-mutator/vitest-runner/-/vitest-runner-10.0.0.tgz",
+ "integrity": "sha512-SHK2/vfvRUpiz7jXPnQMBnr6zLdm69DK03Mo5mPhaZWcRSygrKUqYsPqWsXsK+5ySHzlMTfCyFK5NQ/X9sJFFw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@stryker-mutator/api": "10.0.0",
+ "@stryker-mutator/util": "10.0.0",
+ "semver": "^7.7.4",
+ "tslib": "~2.8.0"
+ },
+ "engines": {
+ "node": ">=22.0.0"
+ },
+ "peerDependencies": {
+ "@stryker-mutator/core": "10.0.0",
+ "vitest": ">=2.0.0"
+ }
+ },
"node_modules/@toon-format/toon": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@toon-format/toon/-/toon-2.3.1.tgz",
@@ -2880,6 +4077,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/gensync": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@types/gensync/-/gensync-1.0.5.tgz",
+ "integrity": "sha512-MbsRCT7mTikHwKZ0X+LVUTLRrZZRLipTuXEO9qOYO+zmjMVk81axyClMROf6uoPD9MRVu46bx8zoR0Ad9q3NAg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/hast": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz",
@@ -2890,6 +4094,13 @@
"@types/unist": "*"
}
},
+ "node_modules/@types/jsesc": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@types/jsesc/-/jsesc-2.5.1.tgz",
+ "integrity": "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
@@ -3892,6 +5103,16 @@
"node": ">= 14.0.0"
}
},
+ "node_modules/angular-html-parser": {
+ "version": "10.11.0",
+ "resolved": "https://registry.npmjs.org/angular-html-parser/-/angular-html-parser-10.11.0.tgz",
+ "integrity": "sha512-3vERzJ65UFDr3C7uozLJwsNcQS3FS784dSh583oDgDTTZMgXe3/pdyXgKndxiP5R2lvYRfW6gSl145Hnyf2OFA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
"node_modules/ansi-escapes": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz",
@@ -4015,6 +5236,19 @@
],
"license": "MIT"
},
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.11.28",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.28.tgz",
+ "integrity": "sha512-br0LOmjJqr6Rt4AEkadbcNf67Zpo7EPwKsWbjnRrJIa1QYFHQBqM4C7F2WTbV8ahTEcg1Eo+Y0aWjjORInYFyA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
"node_modules/bindings": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
@@ -4099,6 +5333,40 @@
"node": "20 || >=22"
}
},
+ "node_modules/browserslist": {
+ "version": "4.29.3",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.3.tgz",
+ "integrity": "sha512-1R4kiYKXGViqEN0CnoDrXc1StD9niAwu+j2dukWzrD4bJgsD4lDmEp0CRbc6E/vYJIfTHwPmwyaKtVSudICdPA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.11.26",
+ "caniuse-lite": "^1.0.30001813",
+ "electron-to-chromium": "^1.5.439",
+ "node-releases": "^2.0.57",
+ "update-browserslist-db": "^1.3.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
"node_modules/buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
@@ -4170,6 +5438,27 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001815",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001815.tgz",
+ "integrity": "sha512-uDybUEyfgbyJJ93J1vikTylBX31AkdVYeVPeffMQpf8XN7hSfr0oZOn4VFOIMNvtU5+rHvC5Udg2TgtJKHm23Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
"node_modules/canonicalize": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/canonicalize/-/canonicalize-3.0.0.tgz",
@@ -4212,6 +5501,19 @@
"node": ">=18"
}
},
+ "node_modules/chalk": {
+ "version": "5.6.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
+ "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
"node_modules/character-entities-html4": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz",
@@ -4234,6 +5536,13 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/chardet": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.2.0.tgz",
+ "integrity": "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/chownr": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
@@ -4280,6 +5589,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/cli-width": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz",
+ "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
"node_modules/colorette": {
"version": "2.0.20",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
@@ -4308,6 +5627,13 @@
"node": ">=20"
}
},
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/copy-anything": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.1.5.tgz",
@@ -4406,6 +5732,17 @@
"node": ">=6"
}
},
+ "node_modules/des.js": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz",
+ "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.1",
+ "minimalistic-assert": "^1.0.0"
+ }
+ },
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
@@ -4439,6 +5776,13 @@
"node": ">=0.3.1"
}
},
+ "node_modules/diff-match-patch": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz",
+ "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
"node_modules/diff3": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/diff3/-/diff3-0.0.3.tgz",
@@ -4459,6 +5803,13 @@
"node": ">= 0.4"
}
},
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.452",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.452.tgz",
+ "integrity": "sha512-cVvk1YBSj6571VmV6hdJTqj94Y79xSu8cj6Y9t6ae4Y4431z8/7EJFvu3rVp90xhfuv9BpPKUmaLfTo4+FOGBg==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/emoji-regex": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
@@ -4473,6 +5824,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/empathic": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.1.0.tgz",
+ "integrity": "sha512-AnfC1ATldl49/cvZdLPDjBfrRNwbDO05aibiOtzQu3qtlbJtomNLhF30HEtn/7iBz50dlMECqATo3fG0LrdEgw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ }
+ },
"node_modules/end-of-stream": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
@@ -4546,6 +5907,16 @@
"node": ">= 0.4"
}
},
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/estree-walker": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
@@ -4614,6 +5985,33 @@
"node": ">=22"
}
},
+ "node_modules/execa": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz",
+ "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sindresorhus/merge-streams": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "figures": "^6.1.0",
+ "get-stream": "^9.0.0",
+ "human-signals": "^8.0.1",
+ "is-plain-obj": "^4.1.0",
+ "is-stream": "^4.0.1",
+ "npm-run-path": "^6.0.0",
+ "pretty-ms": "^9.2.0",
+ "signal-exit": "^4.1.0",
+ "strip-final-newline": "^4.0.0",
+ "yoctocolors": "^2.1.1"
+ },
+ "engines": {
+ "node": "^18.19.0 || >=20.5.0"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
"node_modules/expand-template": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
@@ -4731,6 +6129,22 @@
}
}
},
+ "node_modules/figures": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz",
+ "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-unicode-supported": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
@@ -4738,6 +6152,16 @@
"license": "MIT",
"optional": true
},
+ "node_modules/flru": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/flru/-/flru-1.0.2.tgz",
+ "integrity": "sha512-kWyh8ADvHBFz6ua5xYOPnUroZTT/bwWfrCeL0Wj1dzG4/YOmOcfJ99W8dOVyyynJN35rZ9aCOtHChqQovV7yog==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/focus-trap": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.8.0.tgz",
@@ -4827,6 +6251,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/get-east-asian-width": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz",
@@ -4877,6 +6311,23 @@
"node": ">= 0.4"
}
},
+ "node_modules/get-stream": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz",
+ "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sec-ant/readable-stream": "^0.4.1",
+ "is-stream": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/get-tsconfig": {
"version": "4.14.3",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.3.tgz",
@@ -5042,6 +6493,16 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/human-signals": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz",
+ "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
"node_modules/husky": {
"version": "9.1.7",
"resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz",
@@ -5058,6 +6519,23 @@
"url": "https://github.com/sponsors/typicode"
}
},
+ "node_modules/iconv-lite": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz",
+ "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@@ -5087,6 +6565,17 @@
"node": ">= 4"
}
},
+ "node_modules/import-meta-resolve": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz",
+ "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
@@ -5171,6 +6660,32 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/is-plain-obj": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
+ "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-stream": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz",
+ "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/is-typed-array": {
"version": "1.1.15",
"resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz",
@@ -5186,6 +6701,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-unicode-supported": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz",
+ "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/isarray": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
@@ -5251,6 +6779,13 @@
"url": "https://github.com/sponsors/panva"
}
},
+ "node_modules/js-md4": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz",
+ "integrity": "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/js-tokens": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz",
@@ -5363,12 +6898,45 @@
"win32"
]
},
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-rpc-2.0": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/json-rpc-2.0/-/json-rpc-2.0-1.8.1.tgz",
+ "integrity": "sha512-BmN42JKn6FI36pcMMzHCXQhpKNEfbeqgSIcUYHKnifq0/4Ey6PDinovwUOgajT7NsgUtpWdDXaCWlpI4LHXN3g==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"license": "MIT"
},
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/jsonc-parser": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz",
@@ -5745,6 +7313,13 @@
"node": ">=20.0.0"
}
},
+ "node_modules/lodash.groupby": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz",
+ "integrity": "sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/log-update": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz",
@@ -6026,6 +7601,13 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/minimalistic-assert": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
+ "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/minimatch": {
"version": "10.2.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
@@ -6089,6 +7671,53 @@
"license": "MIT",
"optional": true
},
+ "node_modules/mutation-server-protocol": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/mutation-server-protocol/-/mutation-server-protocol-0.4.1.tgz",
+ "integrity": "sha512-SBGK0j8hLDne7bktgThKI8kGvGTx3rY3LAeQTmOKZ5bVnL/7TorLMvcVF7dIPJCu5RNUWhkkuF53kurygYVt3g==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "zod": "^4.1.12"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/mutation-testing-elements": {
+ "version": "3.8.4",
+ "resolved": "https://registry.npmjs.org/mutation-testing-elements/-/mutation-testing-elements-3.8.4.tgz",
+ "integrity": "sha512-5CF1SNa7at5ZH33vEr+21wNebTSrtNIVvnzaUlxortHajOrIPaSLczIWvg6sI/fsExekQ7jookwf2cHftkckqQ==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/mutation-testing-metrics": {
+ "version": "3.8.4",
+ "resolved": "https://registry.npmjs.org/mutation-testing-metrics/-/mutation-testing-metrics-3.8.4.tgz",
+ "integrity": "sha512-DZcmndJBH6nrNs3tpiB3OcMVq9KkG2cHCpJSnDxSxPwi9qrafRmoec40xjhkbzOoX1n7/4UkDqg5tIj4A6nvCw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "mutation-testing-report-schema": "3.8.4"
+ }
+ },
+ "node_modules/mutation-testing-report-schema": {
+ "version": "3.8.4",
+ "resolved": "https://registry.npmjs.org/mutation-testing-report-schema/-/mutation-testing-report-schema-3.8.4.tgz",
+ "integrity": "sha512-s4G71R6Lt/PpZ0cqeglIcgyBdzLM8E+SeCHZAPg1wkSsPtRBa4XfPzAozYKdiJk/TLbNEEb7En9t0/bveuPuxA==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/mute-stream": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-3.0.0.tgz",
+ "integrity": "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^20.17.0 || >=22.9.0"
+ }
+ },
"node_modules/nanoid": {
"version": "3.3.20",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz",
@@ -6128,6 +7757,59 @@
"node": ">=10"
}
},
+ "node_modules/node-releases": {
+ "version": "2.0.58",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.58.tgz",
+ "integrity": "sha512-BdiKAgbfRlvfMq/XzO/AF9NPmaA8a+YpLAyAFTVFBj3gARrAIfWvNq7Q7NE4se3KgFu0KkrvQMxTS0XVnsGzHg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/npm-run-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz",
+ "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^4.0.0",
+ "unicorn-magic": "^0.3.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/npm-run-path/node_modules/path-key": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
+ "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/obug": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/obug/-/obug-2.2.1.tgz",
@@ -6380,6 +8062,19 @@
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
"license": "(MIT AND Zlib)"
},
+ "node_modules/parse-ms": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz",
+ "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/parse5": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz",
@@ -6621,6 +8316,22 @@
"node": ">=10"
}
},
+ "node_modules/pretty-ms": {
+ "version": "9.3.1",
+ "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.1.tgz",
+ "integrity": "sha512-HzMy3Geq23nVALD/M2LliU+F+M+gVNsvkQWWqeBZ8HDiCgzo6YPJ/Omrmtq24EFrIsk0a3EkQGEd7bDOo+IhGA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parse-ms": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
@@ -6646,6 +8357,16 @@
],
"license": "MIT"
},
+ "node_modules/progress": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/property-information": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz",
@@ -6685,6 +8406,22 @@
],
"license": "MIT"
},
+ "node_modules/qs": {
+ "version": "6.15.1",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz",
+ "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/quick-format-unescaped": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
@@ -6889,6 +8626,16 @@
"fsevents": "~2.3.2"
}
},
+ "node_modules/rxjs": {
+ "version": "7.8.2",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
+ "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.1.0"
+ }
+ },
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -6918,6 +8665,13 @@
"node": ">=10"
}
},
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/search-insights": {
"version": "2.17.3",
"resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz",
@@ -7013,6 +8767,82 @@
"@types/hast": "^3.0.4"
}
},
+ "node_modules/side-channel": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
+ "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4",
+ "side-channel-list": "^1.0.1",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
+ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
@@ -7114,6 +8944,16 @@
"atomic-sleep": "^1.0.0"
}
},
+ "node_modules/source-map": {
+ "version": "0.7.6",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
+ "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
"node_modules/source-map-js": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz",
@@ -7249,6 +9089,19 @@
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
+ "node_modules/strip-final-newline": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz",
+ "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/strip-json-comments": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz",
@@ -7416,6 +9269,16 @@
"integrity": "sha512-NLdXTEZkKiO0gZuLtMoZKjCXTREXeZZt8nnnNeyoXtNZAfG/GKGSbQtLU5STspc0rMSwcA+UJfWZkbNU01iKmQ==",
"license": "MIT"
},
+ "node_modules/tree-kill": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
+ "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "tree-kill": "cli.js"
+ }
+ },
"node_modules/trim-lines": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
@@ -7432,8 +9295,17 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"dev": true,
- "license": "0BSD",
- "optional": true
+ "license": "0BSD"
+ },
+ "node_modules/tunnel": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
+ "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6.11 <=0.7.0 || >=0.7.3"
+ }
},
"node_modules/tunnel-agent": {
"version": "0.6.0",
@@ -7480,6 +9352,33 @@
"node": ">= 0.4"
}
},
+ "node_modules/typed-inject": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/typed-inject/-/typed-inject-5.0.0.tgz",
+ "integrity": "sha512-0Ql2ORqBORLMdAW89TQKZsb1PQkFGImFfVmncXWe7a+AA3+7dh7Se9exxZowH4kbnlvKEFkMxUYdHUpjYWFJaA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/typed-rest-client": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-2.3.1.tgz",
+ "integrity": "sha512-k4kX5Up6qA68D0Cby2AK+6+vM5k3qTxe+/3FqhnHRExjY5cfbOnzjQZbP/LXleF8hVoDvDqxlgk9KK83HoBZlQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "des.js": "^1.1.0",
+ "js-md4": "^0.3.2",
+ "qs": "6.15.1",
+ "tunnel": "0.0.6",
+ "underscore": "^1.13.8"
+ },
+ "engines": {
+ "node": ">= 16.0.0"
+ }
+ },
"node_modules/typescript": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz",
@@ -7530,6 +9429,21 @@
"node": ">=14.17"
}
},
+ "node_modules/typescript6": {
+ "name": "typescript",
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
+ "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
"node_modules/unbash": {
"version": "4.0.12",
"resolved": "https://registry.npmjs.org/unbash/-/unbash-4.0.12.tgz",
@@ -7540,6 +9454,13 @@
"node": ">=14"
}
},
+ "node_modules/underscore": {
+ "version": "1.13.8",
+ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz",
+ "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/undici-types": {
"version": "8.9.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz",
@@ -7547,6 +9468,19 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/unicorn-magic": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz",
+ "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/unist-util-is": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz",
@@ -7620,6 +9554,37 @@
"url": "https://opencollective.com/unified"
}
},
+ "node_modules/update-browserslist-db": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.4.tgz",
+ "integrity": "sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -7627,6 +9592,19 @@
"license": "MIT",
"optional": true
},
+ "node_modules/verkit": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/verkit/-/verkit-0.5.0.tgz",
+ "integrity": "sha512-u4J1hr6Y+C/hit34GwL3eIcUf3wngT8TlYUjK7tk0jxydKLAq3sYvifjdTDA6U3KCKRcQCQDGf/srjxfZF3l7g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sxzz"
+ }
+ },
"node_modules/vfile": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
@@ -8466,6 +10444,13 @@
"node": "20 || >=22"
}
},
+ "node_modules/weapon-regex": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/weapon-regex/-/weapon-regex-2.0.6.tgz",
+ "integrity": "sha512-iJC2vqYp+bh3r4X6zjuP0h65XqXjgUX3nJgmhj0PQMrdhAbUwCfN76CSTEEQqV3G7468kjOJ69PmytTG8l/8Gg==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -8615,6 +10600,19 @@
"url": "https://github.com/sponsors/eemeli"
}
},
+ "node_modules/yoctocolors": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.2.0.tgz",
+ "integrity": "sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/zod": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
diff --git a/package.json b/package.json
index 02307e64e..dff275eb4 100644
--- a/package.json
+++ b/package.json
@@ -49,6 +49,7 @@
"dist",
"install.sh",
"bridge/hopper_bridge.py",
+ "bridge/cutter/rea_bridge.py",
"bridge/native/NativeUIChildren.swift",
"bridge/native/ReaNativeUI.swift",
"bridge/native/rea_lldb_tracer.py",
@@ -149,6 +150,10 @@
"test:integration": "vitest run --project boundary --project cli-boundary --project process-boundary --project mcp-boundary --project process-global --project acceptance",
"pretest:coverage": "npm run test:prepare",
"test:coverage": "vitest run --coverage.enabled --coverage.provider=v8",
+ "test:coverage:reverse-tools": "vitest run --config vitest.reverse-tools.config.ts --coverage.enabled",
+ "pretest:coverage:reverse-tools": "npm run test:prepare",
+ "test:mutation:reverse-tools": "node scripts/prepare-reverse-tools-mutation.mjs && stryker run stryker.config.mjs && stryker run stryker.semantics.config.mjs",
+ "pretest:mutation:reverse-tools": "npm run test:prepare",
"test:ci": "npm run test:coverage -- --reporter=default --reporter=junit --outputFile.junit=test-results.xml",
"pretest:ci:shard": "npm run test:prepare",
"test:ci:shard": "npm run test:ci:shard:run",
@@ -257,6 +262,7 @@
"build:windows-native": "node scripts/build-windows-native.mjs",
"verify:windows-native:artifact": "node scripts/verify-windows-native-artifact.mjs",
"prepublishOnly": "npm run verify:windows-native:artifact",
+ "verify:cutter": "npm run build:cached && node scripts/verify-real-cutter.mjs",
"verify:windows-native": "node scripts/verify-windows-native.mjs",
"verify:ghidra:windows:package": "node scripts/verify-packaged-ghidra-windows.mjs",
"build:ghidra:nativeaot": "node scripts/build-ghidra-nativeaot.mjs",
@@ -348,6 +354,9 @@
},
"devDependencies": {
"@fast-check/vitest": "0.5.0",
+ "@stryker-mutator/core": "10.0.0",
+ "@stryker-mutator/typescript-checker": "10.0.0",
+ "@stryker-mutator/vitest-runner": "10.0.0",
"@types/cross-spawn": "6.0.6",
"@types/node": "26.6.4",
"@types/semver": "7.7.1",
@@ -364,6 +373,7 @@
"turbo": "2.11.7",
"typescript": "7.0.2",
"typescript-termux": "npm:typescript@5.9.3",
+ "typescript6": "npm:typescript@6.0.3",
"vitepress": "1.6.4",
"vitest": "5.0.3"
},
diff --git a/scripts/ci/plan.mjs b/scripts/ci/plan.mjs
index 86879216d..6e3f950b8 100644
--- a/scripts/ci/plan.mjs
+++ b/scripts/ci/plan.mjs
@@ -62,6 +62,10 @@ function classifyPackage(base, head) {
]),
].filter((key) => beforeScripts?.[key] !== afterScripts?.[key]);
for (const script of changed) {
+ if (/^test:(coverage|mutation):reverse-tools$/.test(script)) {
+ selected.reverse_tools = true;
+ continue;
+ }
if (script === "test:sqlite" || script === "test:sqlite:run") {
selected.sqlite = true;
continue;
@@ -162,6 +166,7 @@ if (tier === "reused") {
.split("\0")
.filter(Boolean);
for (const file of files) {
+ if (matchesAny(file, SCOPES.reverse_tools)) selected.reverse_tools = true;
if (
/^(README(?:_[^/]+)?\.md|AGENTS\.md|CONTRIBUTING\.md|CHANGELOG\.md|SECURITY\.md|native\/windows\/README\.md|\.github\/pull_request_template\.md)$/.test(
file,
diff --git a/scripts/ci/required.mjs b/scripts/ci/required.mjs
index 01907de37..20fbac23f 100644
--- a/scripts/ci/required.mjs
+++ b/scripts/ci/required.mjs
@@ -31,6 +31,7 @@ if (outputs.tier === "full" && selectedScopes.length !== CI_FLAGS.length)
const required = reused ? ["changes"] : ["changes", "static", "test"];
const lanes = {
code: ["build", "test-shard"],
+ reverse_tools: ["reverse-tools-quality"],
runtime: ["windows-native-build", "package-e2e"],
package: ["windows-curated"],
sqlite: ["sqlite-runtime"],
diff --git a/scripts/ci/scopes.mjs b/scripts/ci/scopes.mjs
index a4f0f0fac..b13057b33 100644
--- a/scripts/ci/scopes.mjs
+++ b/scripts/ci/scopes.mjs
@@ -2,6 +2,62 @@
// Shared build inputs are classified once in plan.mjs rather than repeated in
// every workflow.
export const SCOPES = {
+ reverse_tools: [
+ "src/application/reverse/**",
+ "src/application/binary/BinarySession.ts",
+ "src/cutter/**",
+ "src/gdb/**",
+ "src/frida/frida-bindings.d.ts",
+ "src/objdump/**",
+ "src/cli/reverseEngineeringCommands.ts",
+ "src/process/ownedDebuggerSession.ts",
+ "src/rizin/**",
+ "src/contracts/*ToolContracts.ts",
+ "src/server/registerCutterTools.ts",
+ "src/server/externalMutationAdmission.ts",
+ "src/server/registerGdbTools.ts",
+ "src/server/registerReverseEngineeringTools.ts",
+ "src/server/providerEnvironment.ts",
+ "src/process/ExecFileOutput.ts",
+ "src/process/ExecFileOutput.test.ts",
+ "tests/composition/analysis-sessions/binarySession.test.ts",
+ "tests/boundary/filesystem/directAnalysisSnapshotReplay.test.ts",
+ "tests/boundary/filesystem/reverseToolsMutationConfig.test.ts",
+ "src/server/registerRizinDebugTools.ts",
+ "tests/boundary/providers/CutterBridge*.test.ts",
+ "tests/boundary/providers/cutterBridgeProducer.test.ts",
+ "tests/boundary/providers/GdbSession*.test.ts",
+ "tests/boundary/providers/GdbCommandQueue.test.ts",
+ "tests/boundary/providers/gdbTestHelpers.ts",
+ "tests/boundary/providers/RizinDebugSessionCoverage.part*.test.ts",
+ "tests/boundary/providers/RizinCommandTimeout.test.ts",
+ "tests/boundary/providers/RizinTimeoutEvidence.test.ts",
+ "tests/boundary/providers/RizinDebugSession*.test.ts",
+ "tests/boundary/providers/RizinInferiorTty.test.ts",
+ "tests/boundary/providers/RizinTestInferiorTty.ts",
+ "tests/boundary/process/ownedDebuggerSession.test.ts",
+ "tests/boundary/cli/gdbStartupCleanupCli.test.ts",
+ "tests/boundary/cli/reverseEngineeringCommands.test.ts",
+ "tests/boundary/mcp/cutter*.test.ts",
+ "tests/boundary/mcp/gdbCancellationMcp.test.ts",
+ "tests/boundary/mcp/debuggerCancellationMcp.test.ts",
+ "tests/boundary/mcp/rizinDebuggerCancellationMcp.test.ts",
+ "tests/boundary/mcp/reverseEngineeringPartialMcp.test.ts",
+ "tests/boundary/mcp/workflowSnapshotReplayMcp.test.ts",
+ "tests/boundary/mcp/reverseEngineeringEnvironmentMcp.test.ts",
+ "tests/support/reverseEngineering*.ts",
+ "tests/support/ownedRizinCommandEvidenceTests.ts",
+ "tests/support/reverseDebuggerMcpTestHelpers.ts",
+ "tests/processOwnershipRunner.ts",
+ "tests/composition/reverseEngineering*.test.ts",
+ "vitest.reverse-tools.config.ts",
+ "vitest.reverse-tools-semantics.config.ts",
+ "stryker.config.mjs",
+ "stryker.semantics.config.mjs",
+ "tsconfig.reverse-tools-mutation.json",
+ "scripts/prepare-reverse-tools-mutation.mjs",
+ "scripts/lib/reverse-tools-mutation-targets.mjs",
+ ],
// Apple artifact CLI/MCP lane on macOS, including its focused regressions.
apple: [
"src/native/Apple*",
diff --git a/scripts/lib/catalog-builders.mjs b/scripts/lib/catalog-builders.mjs
index e480665e3..48f90c6e7 100644
--- a/scripts/lib/catalog-builders.mjs
+++ b/scripts/lib/catalog-builders.mjs
@@ -19,6 +19,11 @@ export const toolFamilyCatalog = (sources) => {
contracts: [
...sources.nativeContracts.NATIVE_TOOL_CONTRACTS,
...sources.binaryDiagnosticsContracts.BINARY_DIAGNOSTICS_TOOL_CONTRACTS,
+ ...sources.cutterContracts.CUTTER_TOOL_CONTRACTS,
+ ...sources.reverseEngineeringContracts
+ .REVERSE_ENGINEERING_TOOL_CONTRACTS,
+ ...sources.gdbContracts.GDB_TOOL_CONTRACTS,
+ ...sources.rizinDebugContracts.RIZIN_DEBUG_TOOL_CONTRACTS,
],
},
{
@@ -164,6 +169,32 @@ export const providerCatalog = (sources) => {
activeContracts,
} = electronContractSlices(sources);
return [
+ {
+ identity: sources.cutterProvider.CUTTER_PROVIDER_IDENTITY,
+ contracts: sources.cutterContracts.CUTTER_TOOL_CONTRACTS,
+ },
+ {
+ identity: sources.objdumpProvider.OBJDUMP_PROVIDER_IDENTITY,
+ contracts:
+ sources.reverseEngineeringContracts.REVERSE_ENGINEERING_TOOL_CONTRACTS.filter(
+ ({ name }) => name === "inspect_with_objdump",
+ ),
+ },
+ {
+ identity: sources.rizinProvider.RIZIN_PROVIDER_IDENTITY,
+ contracts:
+ sources.reverseEngineeringContracts.REVERSE_ENGINEERING_TOOL_CONTRACTS.filter(
+ ({ name }) => name === "execute_rizin_command",
+ ),
+ },
+ {
+ identity: sources.gdbProvider.GDB_PROVIDER_IDENTITY,
+ contracts: sources.gdbContracts.GDB_TOOL_CONTRACTS,
+ },
+ {
+ identity: sources.rizinDebugProvider.RIZIN_DEBUGGER_PROVIDER_IDENTITY,
+ contracts: sources.rizinDebugContracts.RIZIN_DEBUG_TOOL_CONTRACTS,
+ },
{
identity: sources.fridaProvider.FRIDA_PROVIDER_IDENTITY,
contracts: sources.fridaContracts.FRIDA_TOOL_CONTRACTS,
diff --git a/scripts/lib/catalog-core.mjs b/scripts/lib/catalog-core.mjs
index 1d402ed4e..262493b73 100644
--- a/scripts/lib/catalog-core.mjs
+++ b/scripts/lib/catalog-core.mjs
@@ -43,6 +43,16 @@ const SOURCE_PATHS = {
sessionContracts: "dist/contracts/sessionToolContracts.js",
binaryDiagnosticsContracts:
"dist/contracts/native/binaryDiagnosticsToolContracts.js",
+ cutterContracts: "dist/contracts/cutterToolContracts.js",
+ cutterProvider: "dist/cutter/CutterBridgeClient.js",
+ reverseEngineeringContracts:
+ "dist/contracts/reverseEngineeringToolContracts.js",
+ gdbContracts: "dist/contracts/gdbToolContracts.js",
+ gdbProvider: "dist/gdb/GdbSessionManager.js",
+ rizinDebugContracts: "dist/contracts/rizinDebugToolContracts.js",
+ objdumpProvider: "dist/objdump/ObjdumpCommand.js",
+ rizinProvider: "dist/rizin/RizinCommand.js",
+ rizinDebugProvider: "dist/rizin/RizinDebugSessionManager.js",
fridaContracts: "dist/contracts/fridaToolContracts.js",
fridaProvider: "dist/frida/FridaInstrumentationManager.js",
pwntoolsProvider: "dist/native/pwntools/PwntoolsRelease.js",
diff --git a/scripts/lib/reverse-tools-mutation-targets.mjs b/scripts/lib/reverse-tools-mutation-targets.mjs
new file mode 100644
index 000000000..cccf986ac
--- /dev/null
+++ b/scripts/lib/reverse-tools-mutation-targets.mjs
@@ -0,0 +1,182 @@
+import { readFileSync } from "node:fs";
+import ts from "typescript6";
+
+const compact = (text) => text.replace(/\s+/gu, "");
+const nameOf = (node, source) => node.name?.getText(source);
+
+const methodOwner = (node, source) => {
+ for (let parent = node; parent !== undefined; parent = parent.parent) {
+ if (ts.isMethodDeclaration(parent) || ts.isConstructorDeclaration(parent))
+ return {
+ owner: nameOf(parent.parent, source),
+ body: parent.body,
+ method: ts.isConstructorDeclaration(parent)
+ ? "constructor"
+ : nameOf(parent, source),
+ };
+ }
+ return undefined;
+};
+
+const withinOwner = (node, selector, source) => {
+ if (selector.owner === undefined) return true;
+ const owner = methodOwner(node, source);
+ return (
+ owner?.owner === selector.owner &&
+ (selector.method === undefined || owner.method === selector.method)
+ );
+};
+
+const contains = (node, predicate) =>
+ predicate(node) ||
+ ts.forEachChild(node, (child) => contains(child, predicate)) === true;
+
+const inBlockWithBinding = (node, binding) => {
+ if (binding === undefined) return true;
+ let block = node.parent;
+ while (block !== undefined && !ts.isBlock(block)) block = block.parent;
+ return block?.statements.some(
+ (statement) =>
+ ts.isVariableStatement(statement) &&
+ statement.declarationList.declarations.some(
+ (declaration) =>
+ ts.isIdentifier(declaration.name) &&
+ declaration.name.text === binding,
+ ),
+ );
+};
+
+const isCommandHandler = (node, command) => {
+ if (!ts.isArrowFunction(node)) return false;
+ const property = node.parent;
+ if (
+ !ts.isPropertyAssignment(property) ||
+ !ts.isIdentifier(property.name) ||
+ property.name.text !== "run"
+ )
+ return false;
+ const call = property.parent.parent;
+ if (
+ !ts.isCallExpression(call) ||
+ !ts.isPropertyAccessExpression(call.expression) ||
+ !ts.isIdentifier(call.expression.expression) ||
+ call.expression.expression.text !== "cli" ||
+ call.expression.name.text !== "command"
+ )
+ return false;
+ const selected = call.arguments[0];
+ return (
+ selected !== undefined &&
+ ts.isPropertyAccessExpression(selected) &&
+ ts.isIdentifier(selected.expression) &&
+ selected.expression.text === "CLI_COMMANDS" &&
+ selected.name.text === command
+ );
+};
+
+const matches = (node, selector, source) => {
+ if (!withinOwner(node, selector, source)) return false;
+ if (
+ selector.direct === true &&
+ node.parent !== methodOwner(node, source)?.body
+ )
+ return false;
+ switch (selector.kind) {
+ case "expression":
+ return (
+ ts.isBinaryExpression(node) &&
+ compact(node.getText(source)) === compact(selector.text)
+ );
+ case "declaration":
+ return (
+ ts.isVariableDeclaration(node) &&
+ nameOf(node, source) === selector.name &&
+ inBlockWithBinding(node, selector.withinBlockWithBinding)
+ );
+ case "method":
+ return (
+ ts.isMethodDeclaration(node) && nameOf(node, source) === selector.name
+ );
+ case "constructor":
+ return ts.isConstructorDeclaration(node);
+ case "guard":
+ return (
+ ts.isIfStatement(node) &&
+ compact(node.expression.getText(source)) === compact(selector.condition)
+ );
+ case "call":
+ return (
+ ts.isCallExpression(node) &&
+ compact(node.expression.getText(source)) === compact(selector.callee) &&
+ (selector.event === undefined ||
+ (node.arguments[0] !== undefined &&
+ ts.isStringLiteral(node.arguments[0]) &&
+ node.arguments[0].text === selector.event))
+ );
+ case "try":
+ return (
+ ts.isTryStatement(node) &&
+ contains(node.tryBlock, (child) =>
+ selector.assignment !== undefined
+ ? ts.isBinaryExpression(child) &&
+ child.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
+ ts.isIdentifier(child.left) &&
+ child.left.text === selector.assignment
+ : ts.isCallExpression(child) &&
+ compact(child.expression.getText(source)) ===
+ compact(selector.callee),
+ )
+ );
+ case "command-handler":
+ return isCommandHandler(node, selector.command);
+ default:
+ throw new Error(
+ `Unknown mutation selector kind: ${String(selector.kind)}`,
+ );
+ }
+};
+
+/** Parse a source once and resolve one owned region for every requested selector. */
+export const mutationRanges = (path, selectors) => {
+ if (!Array.isArray(selectors) || selectors.length === 0)
+ throw new Error(`Mutation selectors must be nonempty: ${path}`);
+ for (const selector of selectors)
+ if (
+ ["method", "constructor", "guard", "call", "try"].includes(
+ selector.kind,
+ ) &&
+ typeof selector.owner !== "string"
+ )
+ throw new Error(
+ `Mutation selector requires a named class owner: ${path}`,
+ );
+ const source = ts.createSourceFile(
+ path,
+ readFileSync(path, "utf8"),
+ ts.ScriptTarget.Latest,
+ true,
+ );
+ if (source.parseDiagnostics.length !== 0)
+ throw new Error(
+ `Cannot select mutations from malformed TypeScript: ${path}`,
+ );
+ const found = selectors.map(() => []);
+ const visit = (node) => {
+ for (const [index, selector] of selectors.entries())
+ if (matches(node, selector, source)) found[index].push(node);
+ ts.forEachChild(node, visit);
+ };
+ visit(source);
+ return selectors.map((selector, index) => {
+ const candidates = found[index];
+ if (candidates.length !== 1)
+ throw new Error(
+ `Expected one mutation target in ${path}: ${JSON.stringify(selector)}; found ${candidates.length}`,
+ );
+ const node = candidates[0];
+ const first =
+ source.getLineAndCharacterOfPosition(node.getStart(source)).line + 1;
+ const last = source.getLineAndCharacterOfPosition(node.getEnd()).line + 1;
+ return `${path}:${first}-${last}`;
+ });
+};
diff --git a/scripts/prepare-reverse-tools-mutation.mjs b/scripts/prepare-reverse-tools-mutation.mjs
new file mode 100644
index 000000000..a591138a5
--- /dev/null
+++ b/scripts/prepare-reverse-tools-mutation.mjs
@@ -0,0 +1,19 @@
+import { mkdir, rm, symlink } from "node:fs/promises";
+import { dirname, resolve } from "node:path";
+import { fileURLToPath } from "node:url";
+
+const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
+const compiler = resolve(root, "node_modules/typescript6");
+
+for (const packageName of ["core", "typescript-checker"]) {
+ const packagePath = resolve(
+ root,
+ "node_modules/@stryker-mutator",
+ packageName,
+ );
+ const nodeModulesPath = resolve(packagePath, "node_modules");
+ const typescriptPath = resolve(nodeModulesPath, "typescript");
+ await mkdir(nodeModulesPath, { recursive: true });
+ await rm(typescriptPath, { recursive: true, force: true });
+ await symlink(compiler, typescriptPath, "dir");
+}
diff --git a/scripts/verify-packed-bridge.mjs b/scripts/verify-packed-bridge.mjs
index 0ed18eaa3..b83dc7715 100644
--- a/scripts/verify-packed-bridge.mjs
+++ b/scripts/verify-packed-bridge.mjs
@@ -4,6 +4,7 @@ import { promisify } from "node:util";
const exec = promisify(execFile);
const packedHopperBridge = "package/bridge/hopper_bridge.py";
+const packedCutterBridge = "package/bridge/cutter/rea_bridge.py";
const packedGhidraBridge = "package/bridge/ghidra/ReaGhidraBridge.java";
const packedGhidraActivator =
"package/bridge/ghidra/ReaGhidraBundleActivator.java";
@@ -12,6 +13,7 @@ const packedGhidraActivator =
export async function verifyPackedBridge({ workspace, tarball, packedFiles }) {
for (const path of [
packedHopperBridge,
+ packedCutterBridge,
packedGhidraBridge,
packedGhidraActivator,
]) {
diff --git a/scripts/verify-real-cutter.mjs b/scripts/verify-real-cutter.mjs
new file mode 100644
index 000000000..5ffde438d
--- /dev/null
+++ b/scripts/verify-real-cutter.mjs
@@ -0,0 +1,214 @@
+#!/usr/bin/env node
+
+import assert from "node:assert/strict";
+import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
+import { execFile } from "node:child_process";
+import { createHash, randomUUID } from "node:crypto";
+import {
+ access,
+ constants,
+ mkdtemp,
+ readFile,
+ rm,
+ writeFile,
+} from "node:fs/promises";
+import { tmpdir } from "node:os";
+import { join, resolve } from "node:path";
+import { promisify } from "node:util";
+import { createServer } from "../dist/server/createServer.js";
+
+const execFileAsync = promisify(execFile);
+const cli = resolve("scripts/rea.mjs");
+const runCli = async (...arguments_) => {
+ const { stdout } = await execFileAsync(
+ process.execPath,
+ [cli, ...arguments_, "--format", "json"],
+ {
+ encoding: "utf8",
+ maxBuffer: 4 * 1024 * 1024,
+ env: process.env,
+ },
+ );
+ return JSON.parse(stdout);
+};
+
+const projectRoot = await mkdtemp(join(tmpdir(), "rea-cutter-smoke-"));
+const projectPath = join(projectRoot, "smoke.rzdb");
+const rizinQuote = (value) =>
+ `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
+const unavailableAnalysis = {
+ execute: async () => {
+ throw new Error(
+ "The Cutter verification does not call unrelated analysis tools.",
+ );
+ },
+};
+const expectedPlatform = {
+ Linux: "linux",
+ macOS: "darwin",
+ Windows: "win32",
+}[process.env.EXPECTED_PLATFORM];
+if (expectedPlatform !== undefined)
+ assert.equal(
+ process.platform,
+ expectedPlatform,
+ "Cutter runner did not match its declared platform.",
+ );
+try {
+ const discovery = await runCli("list-cutter-sessions");
+ assert.equal(
+ discovery.discovery_status,
+ "sessions_found",
+ "Open upstream Cutter with the REA plugin before running this smoke test.",
+ );
+ assert.ok(discovery.sessions.length > 0);
+ const session = discovery.sessions[0];
+ assert.ok(session);
+ assert.ok(
+ session.cutter_version,
+ "The upstream Cutter API did not report its version.",
+ );
+ if (process.env.REA_CUTTER_EXPECTED_VERSION !== undefined)
+ assert.ok(
+ session.cutter_version.includes(process.env.REA_CUTTER_EXPECTED_VERSION),
+ "The live Cutter version did not match the configured version.",
+ );
+ assert.ok(
+ session.current_file,
+ "Open a disposable local binary in Cutter before running this smoke test.",
+ );
+ const artifactPath = session.current_file;
+ const artifactDigest = createHash("sha256")
+ .update(await readFile(artifactPath))
+ .digest("hex");
+
+ const inspection = await runCli(
+ "cutter-command",
+ session.session_id,
+ String(session.document_generation),
+ "ij",
+ "--json",
+ );
+ assert.equal(inspection.predicate_type, "rea.cutter.command-observation");
+ assert.ok(
+ Object.hasOwn(inspection, "raw_result"),
+ "Cutter ij result was not retained as Evidence.",
+ );
+ assert.equal(typeof inspection.raw_result.bin?.arch, "string");
+
+ const mcpServer = createServer(
+ { kind: "fixed", analysis: unavailableAnalysis },
+ {
+ providerEnvironment: process.env,
+ },
+ );
+ const mcpClient = new Client({
+ name: "rea-cutter-upstream-smoke",
+ version: "1",
+ });
+ const [clientTransport, serverTransport] =
+ InMemoryTransport.createLinkedPair();
+ try {
+ await mcpServer.connect(serverTransport);
+ await mcpClient.connect(clientTransport);
+ const mcpDiscovery = await mcpClient.callTool({
+ name: "list_cutter_sessions",
+ arguments: {},
+ });
+ assert.notEqual(mcpDiscovery.isError, true);
+ assert.equal(
+ mcpDiscovery.structuredContent.discovery_status,
+ "sessions_found",
+ );
+ const mcpInspection = await mcpClient.callTool({
+ name: "cutter_command",
+ arguments: {
+ session_id: session.session_id,
+ expected_generation: session.document_generation,
+ command: "ij",
+ json: true,
+ },
+ });
+ assert.notEqual(mcpInspection.isError, true);
+ assert.deepEqual(
+ mcpInspection.structuredContent.normalized_result,
+ inspection.normalized_result,
+ "Cutter CLI and MCP returned different analysis results.",
+ );
+ } finally {
+ await mcpClient.close();
+ await mcpServer.close();
+ }
+
+ const annotation = `rea-smoke-${randomUUID()}`;
+ await runCli(
+ "cutter-command",
+ session.session_id,
+ String(session.document_generation),
+ `CC ${annotation} @ entry0`,
+ );
+ await runCli(
+ "cutter-command",
+ session.session_id,
+ String(session.document_generation),
+ `Ps ${rizinQuote(projectPath)}`,
+ );
+ await access(projectPath, constants.R_OK | constants.W_OK);
+ await runCli(
+ "cutter-command",
+ session.session_id,
+ String(session.document_generation),
+ "CC- @ entry0",
+ );
+ const removed = await runCli(
+ "cutter-command",
+ session.session_id,
+ String(session.document_generation),
+ "CC. @ entry0",
+ );
+ assert.ok(
+ !String(removed.raw_result).includes(annotation),
+ "The in-memory annotation was not removed before project reopening.",
+ );
+ await runCli(
+ "cutter-command",
+ session.session_id,
+ String(session.document_generation),
+ `Po ${rizinQuote(projectPath)}`,
+ );
+ const recovered = await runCli(
+ "cutter-command",
+ session.session_id,
+ String(session.document_generation),
+ "CC. @ entry0",
+ );
+ assert.equal(recovered.predicate_type, "rea.cutter.command-observation");
+ assert.ok(
+ String(recovered.raw_result).includes(annotation),
+ "The saved project did not preserve the analysis comment after reopening.",
+ );
+ const finalArtifactDigest = createHash("sha256")
+ .update(await readFile(artifactPath))
+ .digest("hex");
+ assert.equal(
+ finalArtifactDigest,
+ artifactDigest,
+ "Cutter project edits changed the executable bytes.",
+ );
+
+ const report = {
+ ok: true,
+ platform: process.platform,
+ cutter_version: session.cutter_version,
+ document_generation: session.document_generation,
+ cli_mcp_evidence_parity: true,
+ annotation_saved_and_reopened: true,
+ executable_digest_unchanged: true,
+ };
+ await writeFile("cutter-smoke.json", `${JSON.stringify(report, null, 2)}\n`, {
+ mode: 0o600,
+ });
+ console.log(JSON.stringify(report));
+} finally {
+ await rm(projectRoot, { recursive: true, force: true });
+}
diff --git a/scripts/verify-windows-native.mjs b/scripts/verify-windows-native.mjs
index 0a42cb604..fb4226bf4 100644
--- a/scripts/verify-windows-native.mjs
+++ b/scripts/verify-windows-native.mjs
@@ -148,6 +148,10 @@ process.stdout.write(JSON.stringify({executable:basename(process.execPath),abiVe
"//localhost/share/target.bin",
])
assert.throws(() => native.call("open", [rejected]));
+ assert.throws(
+ () => native.call("cutter_bridge_verify_directory", [sourceDirectory]),
+ /owner|DACL/u,
+ );
report.controls.ordinaryDriveSeparatorsAndRequestedIdentity = true;
// An owned runtime's DELETE lease conflicts with a fresh parent admission.
// This produces a genuine sharing failure after successful authority caching,
@@ -282,6 +286,10 @@ process.stdout.write(JSON.stringify({executable:basename(process.execPath),abiVe
() => native.call("open", [join(junction, "target.bin")]),
/Reparse/u,
);
+ assert.throws(
+ () => native.call("cutter_bridge_verify_directory", [junction]),
+ /Reparse/u,
+ );
assert.throws(() => native.call("open", [source + ":stream"]));
const runtime = native.call("runtime_create", [
workspace.replaceAll("\\", "/"),
@@ -326,6 +334,47 @@ process.stdout.write(JSON.stringify({executable:basename(process.execPath),abiVe
"descriptor.json",
Buffer.from("fixture"),
]);
+ assert.equal(
+ native.call("cutter_bridge_verify_directory", [runtime.path]).privateDacl,
+ true,
+ );
+ assert.equal(
+ native
+ .call("cutter_bridge_read_descriptor", [
+ runtime.path,
+ "descriptor.json",
+ 64 * 1024,
+ ])
+ .toString("utf8"),
+ "fixture",
+ );
+ const descriptorJunction = join(runtime.path, "descriptor-link.json");
+ await symlink(sourceDirectory, descriptorJunction, "junction");
+ assert.throws(() =>
+ native.call("cutter_bridge_read_descriptor", [
+ runtime.path,
+ "descriptor-link.json",
+ 64 * 1024,
+ ]),
+ );
+ assert.throws(
+ () =>
+ native.call("cutter_bridge_read_descriptor", [
+ runtime.path,
+ "descriptor.json",
+ 3,
+ ]),
+ /byte limit/u,
+ );
+ assert.throws(
+ () =>
+ native.call("cutter_bridge_read_descriptor", [
+ runtime.path,
+ "..\\escape.json",
+ 64 * 1024,
+ ]),
+ /filename component/u,
+ );
assert.throws(() =>
native.call("runtime_write", [
runtime.handle,
diff --git a/src/application/CallPathTracing.ts b/src/application/CallPathTracing.ts
index cc4ce41f4..640f7c510 100644
--- a/src/application/CallPathTracing.ts
+++ b/src/application/CallPathTracing.ts
@@ -158,7 +158,7 @@ const expandAddress = async (
state.residual.add(`Call relationships were unavailable for ${address}.`);
return ok(false);
}
- const related = parseRelatedAddresses(result.value.result, relation);
+ const related = parseRelatedAddresses(result.value.result, relation, tool);
if (!related.ok) {
state.failures.push({
address,
diff --git a/src/application/binary/BinarySession.ts b/src/application/binary/BinarySession.ts
index b151958e7..350642016 100644
--- a/src/application/binary/BinarySession.ts
+++ b/src/application/binary/BinarySession.ts
@@ -5,7 +5,10 @@ import {
analysisProfilesEqual,
type AnalysisProfileCommitment,
} from "../../domain/analysisProfile.js";
-import { AnalysisCancelledError } from "../../domain/analysisErrorCore.js";
+import {
+ AnalysisCancelledError,
+ AnalysisOutputError,
+} from "../../domain/analysisErrorCore.js";
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
import { err, ok, type Result } from "../../domain/result.js";
import type { JsonValue } from "../../domain/jsonValue.js";
@@ -73,6 +76,7 @@ export class BinarySession
implements BinarySessionPort
{
#active: SessionBinding | undefined;
+ #externalMutationCleanupFailure: AnalysisError | undefined;
#pendingCleanup:
| {
readonly client: AnalysisClient;
@@ -158,6 +162,10 @@ export class BinarySession
);
}
+ invalidateSnapshotForExternalMutation(): void {
+ this.invalidateSnapshot();
+ }
+
/** Return the selected immutable profile, optionally scoped to an operation. */
analysisProfile(
operation?: AnalysisOperation,
@@ -245,110 +253,180 @@ export class BinarySession
resolve: () => Promise>,
options: Pick,
): Promise> {
- return this.#serialize(async (admittedThrough) => {
- if (isAborted(options.signal))
- return err(new AnalysisCancelledError("open_binary"));
- const resolved = await resolve();
- if (!resolved.ok) return resolved;
- const { target, route, sameTarget } = resolved.value;
- const { profile } = route;
- if (isAborted(options.signal))
- return err(new AnalysisCancelledError("open_binary"));
- const activeProfile = this.#active?.profile;
- const sameProfile =
- activeProfile === null || activeProfile === undefined
- ? profile === null
- : profile !== null && analysisProfilesEqual(activeProfile, profile);
- await this.#drainCalls(admittedThrough);
- if (isAborted(options.signal))
- return err(new AnalysisCancelledError("open_binary"));
- const pendingClosed = await this.#retryPendingCleanup();
- if (!pendingClosed.ok) return pendingClosed;
- if (isAborted(options.signal))
- return err(new AnalysisCancelledError("open_binary"));
- const retained = sameTarget && sameProfile ? this.#active : undefined;
- if (retained !== undefined) {
- const fastPath = await this.#tryRetainedFastPath(
- retained,
- target,
- options,
- );
- if (fastPath !== undefined) return fastPath;
- }
- const previous = this.#active;
- this.#active = undefined;
- if (previous !== undefined) {
- const closed = await this.#retireClient(
- previous.client,
- previous.route.identity.id,
- );
- if (!closed.ok) {
- // A failed open retires the provider cache, not the investigation.
- // Preserve Evidence and Unknown history for export and recovery.
- this.clearSnapshot();
- this.#clearRuntimeAvailability();
- return closed;
- }
- }
- if (isAborted(options.signal))
- return err(
- await this.#restoreAfterFailedOpen(
- previous,
- new AnalysisCancelledError("open_binary"),
- ),
- );
- const runId = randomUUID();
- const client = route.createClient(target, { runId });
- const started = await client.execute("health", {}, options);
- if (!started.ok) {
- return err(
- await this.#failedOpen(
- client,
- route.identity.id,
- previous,
- started.error,
- ),
- );
- }
- if (isAborted(options.signal)) {
- return err(
- await this.#failedOpen(
- client,
- route.identity.id,
- previous,
- new AnalysisCancelledError("open_binary"),
- ),
- );
- }
- this.#active = {
+ return this.#serialize((admittedThrough) =>
+ this.#openWithinTransition(admittedThrough, resolve, options),
+ );
+ }
+
+ async #openWithinTransition(
+ admittedThrough: number,
+ resolve: () => Promise>,
+ options: Pick,
+ ): Promise> {
+ if (this.#externalMutationCleanupFailure?.cleanupIncomplete === true)
+ return err(this.#externalMutationCleanupFailure);
+ if (isAborted(options.signal))
+ return err(new AnalysisCancelledError("open_binary"));
+ const resolved = await resolve();
+ if (!resolved.ok) return resolved;
+ const { target, route, sameTarget } = resolved.value;
+ const { profile } = route;
+ if (isAborted(options.signal))
+ return err(new AnalysisCancelledError("open_binary"));
+ const activeProfile = this.#active?.profile;
+ const sameProfile =
+ activeProfile === null || activeProfile === undefined
+ ? profile === null
+ : profile !== null && analysisProfilesEqual(activeProfile, profile);
+ await this.#drainCalls(admittedThrough);
+ if (isAborted(options.signal))
+ return err(new AnalysisCancelledError("open_binary"));
+ const pendingClosed = await this.#retryPendingCleanup();
+ if (!pendingClosed.ok) return pendingClosed;
+ if (isAborted(options.signal))
+ return err(new AnalysisCancelledError("open_binary"));
+ const retained = sameTarget && sameProfile ? this.#active : undefined;
+ if (retained !== undefined) {
+ const fastPath = await this.#tryRetainedFastPath(
+ retained,
target,
- client,
- profile,
- route,
- runId,
- nativeSourceAdmitted: false,
- };
- this.#clearRuntimeAvailability();
- this.resetSnapshotInvalidation();
- if (options.snapshot === undefined) {
- if (profile === null) this.clearSnapshot();
- else this.selectSnapshot(target, profile);
+ options,
+ );
+ if (fastPath !== undefined) return fastPath;
+ }
+ const previous = this.#active;
+ this.#active = undefined;
+ if (previous !== undefined) {
+ const closed = await this.#retireClient(
+ previous.client,
+ previous.route.identity.id,
+ );
+ if (!closed.ok) {
+ // A failed open retires the provider cache, not the investigation.
+ // Preserve Evidence and Unknown history for export and recovery.
+ this.clearSnapshot();
+ this.#clearRuntimeAvailability();
+ return closed;
}
- if (options.snapshot !== undefined) {
- const imported = this.importAnalysisSnapshot(options.snapshot);
- if (!imported.ok) {
- this.#active = undefined;
- return err(
- await this.#failedOpen(
- client,
- route.identity.id,
- previous,
- imported.error,
- ),
- );
- }
+ }
+ if (isAborted(options.signal))
+ return err(
+ await this.#restoreAfterFailedOpen(
+ previous,
+ new AnalysisCancelledError("open_binary"),
+ ),
+ );
+ const runId = randomUUID();
+ const client = route.createClient(target, { runId });
+ const started = await client.execute("health", {}, options);
+ if (!started.ok) {
+ return err(
+ await this.#failedOpen(
+ client,
+ route.identity.id,
+ previous,
+ started.error,
+ ),
+ );
+ }
+ if (isAborted(options.signal)) {
+ return err(
+ await this.#failedOpen(
+ client,
+ route.identity.id,
+ previous,
+ new AnalysisCancelledError("open_binary"),
+ ),
+ );
+ }
+ this.#active = {
+ target,
+ client,
+ profile,
+ route,
+ runId,
+ nativeSourceAdmitted: false,
+ };
+ this.#clearRuntimeAvailability();
+ this.resetSnapshotInvalidation();
+ if (options.snapshot === undefined) {
+ if (profile === null) this.clearSnapshot();
+ else this.selectSnapshot(target, profile);
+ }
+ if (options.snapshot !== undefined) {
+ const imported = this.importAnalysisSnapshot(options.snapshot);
+ if (!imported.ok) {
+ this.#active = undefined;
+ return err(
+ await this.#failedOpen(
+ client,
+ route.identity.id,
+ previous,
+ imported.error,
+ ),
+ );
}
- return ok(target);
+ }
+ return ok(target);
+ }
+
+ /** Serialize an external mutation and retire the potentially stale analysis. */
+ withAdmittedExternalMutation(
+ _path: string | undefined,
+ signal: AbortSignal | undefined,
+ operation: () => Promise>,
+ retirementFailure: (
+ value: Result,
+ error: AnalysisError,
+ ) => AnalysisError,
+ operationName = "execute_rizin_command",
+ cleanupRecovery?: (
+ failure: AnalysisError | undefined,
+ ) => Promise>,
+ ): Promise, AnalysisError>> {
+ return this.#serialize(async (admittedThrough) => {
+ if (isAborted(signal))
+ return err(new AnalysisCancelledError(operationName));
+ await this.#drainCalls(admittedThrough);
+ if (isAborted(signal))
+ return err(new AnalysisCancelledError(operationName));
+
+ if (this.#externalMutationCleanupFailure?.cleanupIncomplete === true) {
+ if (cleanupRecovery === undefined)
+ return err(this.#externalMutationCleanupFailure);
+ const recovered = await cleanupRecovery(
+ this.#externalMutationCleanupFailure,
+ );
+ if (!recovered.ok) return recovered;
+ if (this.#externalMutationCleanupFailure.cleanupIncomplete)
+ return err(this.#externalMutationCleanupFailure);
+ this.#externalMutationCleanupFailure = undefined;
+ }
+
+ const pendingCleanup = await this.#retryPendingCleanup();
+ if (!pendingCleanup.ok) return pendingCleanup;
+
+ const activeAtDispatch = this.#active;
+ if (activeAtDispatch?.route.binding?.identity.id === "ghidra")
+ return err(
+ new AnalysisOutputError(
+ operationName,
+ "Close the active Ghidra session explicitly before an external mutation; rebinding would discard its ephemeral analysis edits.",
+ ),
+ );
+ // A protocol reply does not prove that background writers have stopped.
+ // Retire the old analysis and require an explicit fresh open rather than
+ // silently binding a snapshot while an unrestricted command can still write.
+ const retireTarget = activeAtDispatch !== undefined;
+ this.invalidateSnapshot();
+ const value = await operation();
+ this.#externalMutationCleanupFailure =
+ !value.ok && value.error.cleanupIncomplete ? value.error : undefined;
+ if (!retireTarget) return ok(value);
+
+ const retired = await this.#closeActive({}, false);
+ if (!retired.ok) return err(retirementFailure(value, retired.error));
+ return ok(value);
});
}
@@ -396,6 +474,7 @@ export class BinarySession
options: Pick & {
readonly retainProviderDocuments?: boolean;
},
+ clearRecords = true,
): Promise> {
const previous = this.#active;
this.#active = undefined;
@@ -417,7 +496,7 @@ export class BinarySession
: {}),
},
);
- this.clearSessionRecords();
+ if (clearRecords) this.clearSessionRecords();
this.#clearRuntimeAvailability();
const fileResources = await this.closeWriteResources();
if (!closed.ok && !fileResources.ok)
diff --git a/src/application/binary/BinarySessionPort.ts b/src/application/binary/BinarySessionPort.ts
index 749e98b2f..49f513c69 100644
--- a/src/application/binary/BinarySessionPort.ts
+++ b/src/application/binary/BinarySessionPort.ts
@@ -48,6 +48,20 @@ export interface BinarySessionPort
signal: AbortSignal | undefined,
operation: (analysis: AnalysisOperationPort) => Promise,
): Promise>;
+ /** Serialize an external mutation and retire any active target afterward. */
+ withAdmittedExternalMutation(
+ _path: string | undefined,
+ signal: AbortSignal | undefined,
+ operation: () => Promise>,
+ retirementFailure: (
+ value: Result,
+ error: AnalysisError,
+ ) => AnalysisError,
+ operationName?: string,
+ cleanupRecovery?: (
+ failure: AnalysisError | undefined,
+ ) => Promise>,
+ ): Promise, AnalysisError>>;
open(
path: string,
options?: {
@@ -82,6 +96,8 @@ export interface BinarySessionPort
operation?: AnalysisOperation,
): AnalysisProfileCommitment | undefined;
allowsSnapshotReplay(operation: AnalysisOperation): boolean;
+ /** Invalidate cached Evidence before an external command may mutate the target. */
+ invalidateSnapshotForExternalMutation(): void;
recordWorkflowSnapshot(
input: WorkflowSnapshotRecordInput,
): Result;
diff --git a/src/application/reverse/ReverseEngineeringCommandPort.ts b/src/application/reverse/ReverseEngineeringCommandPort.ts
new file mode 100644
index 000000000..947af38dd
--- /dev/null
+++ b/src/application/reverse/ReverseEngineeringCommandPort.ts
@@ -0,0 +1,19 @@
+import type { EvidenceProvider } from "../../domain/evidence.js";
+import type {
+ ObjdumpInput,
+ RizinInput,
+} from "../../domain/reverseEngineering.js";
+
+/** Provider-neutral command profile consumed by the shared application workflow. */
+export interface ReverseEngineeringProviderCommand {
+ readonly executable: string;
+ readonly arguments: readonly string[];
+ readonly identity: EvidenceProvider;
+ readonly environmentOverrides?: Readonly;
+}
+
+/** Provider-specific command construction stays outside the application layer. */
+export interface ReverseEngineeringCommandPort {
+ objdump(input: ObjdumpInput): ReverseEngineeringProviderCommand;
+ rizin(input: RizinInput): ReverseEngineeringProviderCommand;
+}
diff --git a/src/application/reverse/ReverseEngineeringService.ts b/src/application/reverse/ReverseEngineeringService.ts
new file mode 100644
index 000000000..41589747b
--- /dev/null
+++ b/src/application/reverse/ReverseEngineeringService.ts
@@ -0,0 +1,948 @@
+import { createHash, randomUUID } from "node:crypto";
+import { createReadStream } from "node:fs";
+import { stat as statPath } from "node:fs/promises";
+import type { Stats } from "node:fs";
+import { resolve } from "node:path";
+
+import {
+ AnalysisAccessDeniedError,
+ AnalysisCancelledError,
+ AnalysisCapabilityUnavailableError,
+ AnalysisInputError,
+ AnalysisResourceConstraintError,
+ AnalysisTimeoutError,
+} from "../../domain/analysisErrorCore.js";
+import type {
+ AnalysisCleanupObservation,
+ AnalysisError,
+} from "../../domain/analysisErrorBase.js";
+import { AnalysisOutputError } from "../../domain/analysisErrorCore.js";
+import { createEvidence, type Evidence } from "../../domain/evidence.js";
+import type { JsonValue } from "../../domain/jsonValue.js";
+import { err, ok, type Result } from "../../domain/result.js";
+import {
+ objdumpInputSchema,
+ rizinInputSchema,
+} from "../../domain/reverseEngineering.js";
+import type {
+ ReverseEngineeringCommandPort,
+ ReverseEngineeringProviderCommand,
+} from "./ReverseEngineeringCommandPort.js";
+import {
+ execFileOutput,
+ execFileOutputFailure,
+} from "../../process/ExecFileOutput.js";
+import {
+ OwnedCommandFailure,
+ runOwnedCommand,
+} from "../../process/OwnedCommand.js";
+import type { ProviderProcessSupervisor } from "../../process/ProviderProcess.js";
+
+export type ReverseEngineeringOperation =
+ | "inspect_with_objdump"
+ | "execute_rizin_command";
+
+const DEFAULT_TIMEOUT_MS = 120_000;
+const MAX_COMBINED_OUTPUT_BYTES = 16 * 1024 * 1024;
+// Eight concurrent captures bound retained provider output to 128 MiB.
+const MAX_CONCURRENT_REVERSE_ENGINEERING_OPERATIONS = 8;
+
+export interface ReverseEngineeringServiceOptions {
+ readonly environment?: Readonly;
+ readonly commands: ReverseEngineeringCommandPort;
+ readonly run?: ReverseEngineeringCommandRunner;
+ readonly runOwned?: typeof runOwnedCommand;
+ readonly digest?: typeof digestFile;
+ readonly stat?: (path: string) => Promise;
+}
+
+interface ReverseEngineeringCommandRunner {
+ (
+ command: string,
+ arguments_: readonly string[],
+ options?: Parameters[2],
+ ): Promise<{
+ readonly stdout: string;
+ readonly stderr: string;
+ readonly stdoutInvalidUtf8?: boolean;
+ readonly stderrInvalidUtf8?: boolean;
+ readonly stdoutBytes?: number;
+ readonly stderrBytes?: number;
+ }>;
+}
+
+export type ReverseEngineeringServiceRuntimeOptions = Omit<
+ ReverseEngineeringServiceOptions,
+ "commands" | "environment"
+>;
+
+/** Shared application workflow for caller-selected local analysis commands. */
+export class ReverseEngineeringService {
+ readonly #environment: Readonly;
+ readonly #commands: ReverseEngineeringCommandPort;
+ readonly #run: ReverseEngineeringCommandRunner;
+ readonly #digest: typeof digestFile;
+ readonly #stat: (path: string) => Promise;
+ readonly #pendingCleanup = new Map<
+ ProviderProcessSupervisor,
+ {
+ readonly observation: AnalysisCleanupObservation;
+ readonly failures: Set;
+ readonly retryFailure?: AnalysisError;
+ }
+ >();
+ readonly #activeOperations = new Map<
+ AbortController,
+ Promise>
+ >();
+ #closing = false;
+ #closePromise: Promise | undefined;
+
+ constructor(options: ReverseEngineeringServiceOptions) {
+ this.#environment = { ...(options.environment ?? process.env) };
+ this.#commands = options.commands;
+ this.#run =
+ options.run ??
+ runOwnedCommandOutput(
+ options.runOwned ?? runOwnedCommand,
+ (owner, cleanup) =>
+ this.#pendingCleanup.set(owner, {
+ observation: cleanup,
+ failures: this.#pendingCleanup.get(owner)?.failures ?? new Set(),
+ }),
+ );
+ this.#digest = options.digest ?? digestFile;
+ this.#stat = options.stat ?? statPath;
+ }
+
+ /** Ask retained process owners to settle cleanup before admitting more work. */
+ async retryCleanup(
+ operation = "reverse_engineering_cleanup",
+ ): Promise> {
+ let failure: AnalysisError | undefined;
+ for (const [owner, retained] of this.#pendingCleanup) {
+ let stopped: Awaited>;
+ try {
+ stopped = await owner.stop();
+ } catch (cause) {
+ stopped = {
+ status: "incomplete",
+ reason: cause instanceof Error ? cause.message : String(cause),
+ };
+ }
+ if (stopped.status === "incomplete") {
+ const observation = {
+ reason: stopped.reason,
+ resources: retained.observation.resources,
+ };
+ let retryFailure = retained.retryFailure;
+ if (retryFailure === undefined) {
+ retryFailure = new AnalysisOutputError(
+ operation,
+ "A previous provider process still requires cleanup.",
+ { cleanup: observation },
+ );
+ retained.failures.add(retryFailure);
+ }
+ this.#pendingCleanup.set(owner, {
+ observation,
+ failures: retained.failures,
+ retryFailure,
+ });
+ if (failure === undefined) failure = retryFailure;
+ } else {
+ for (const previous of retained.failures) previous.resolveCleanup();
+ this.#pendingCleanup.delete(owner);
+ }
+ }
+ return failure === undefined ? ok(null) : err(failure);
+ }
+
+ /** Retry cleanup for provider process groups retained after an incomplete stop. */
+ close(): Promise {
+ this.#closing = true;
+ this.#closePromise ??= this.#closeActiveOperationsAndCleanup().catch(
+ (cause: unknown) => {
+ this.#closePromise = undefined;
+ throw cause;
+ },
+ );
+ return this.#closePromise;
+ }
+
+ async #closeActiveOperationsAndCleanup(): Promise {
+ const active = [...this.#activeOperations.entries()];
+ for (const [controller] of active) controller.abort();
+ await Promise.allSettled(active.map(([, operation]) => operation));
+
+ const cleanup = await this.retryCleanup();
+ if (!cleanup.ok) throw cleanup.error;
+ }
+
+ inspectWithObjdump(
+ rawInput: unknown,
+ options: { readonly signal?: AbortSignal } = {},
+ ): Promise> {
+ const parsed = objdumpInputSchema.safeParse(rawInput);
+ if (!parsed.success)
+ return Promise.resolve(
+ err(new AnalysisInputError("inspect_with_objdump")),
+ );
+ const input = parsed.data;
+ const command = this.#commands.objdump(input);
+ return this.#observe(
+ "inspect_with_objdump",
+ command,
+ input.operation === "architectures" ? undefined : input.path,
+ input,
+ options.signal,
+ );
+ }
+
+ executeRizinCommand(
+ rawInput: unknown,
+ options: { readonly signal?: AbortSignal } = {},
+ ): Promise> {
+ const parsed = rizinInputSchema.safeParse(rawInput);
+ if (!parsed.success)
+ return Promise.resolve(
+ err(new AnalysisInputError("execute_rizin_command")),
+ );
+ const input = parsed.data;
+ const command = this.#commands.rizin(input);
+ return this.#observe(
+ "execute_rizin_command",
+ command,
+ input.path,
+ input,
+ options.signal,
+ );
+ }
+
+ #observe(
+ operation: ReverseEngineeringOperation,
+ profile: ReverseEngineeringProviderCommand,
+ path: string | undefined,
+ parameters: Readonly>,
+ signal?: AbortSignal,
+ ): Promise> {
+ if (signal?.aborted)
+ return Promise.resolve(err(new AnalysisCancelledError(operation)));
+ if (this.#closing)
+ return Promise.resolve(
+ err(
+ new AnalysisOutputError(
+ operation,
+ "Reverse-engineering service is closed.",
+ ),
+ ),
+ );
+ if (
+ this.#activeOperations.size >=
+ MAX_CONCURRENT_REVERSE_ENGINEERING_OPERATIONS
+ )
+ return Promise.resolve(
+ err(
+ new AnalysisResourceConstraintError(
+ operation,
+ "transport",
+ "Concurrent reverse-engineering operation capacity reached.",
+ {
+ max_concurrent_operations:
+ MAX_CONCURRENT_REVERSE_ENGINEERING_OPERATIONS,
+ active_operations: this.#activeOperations.size,
+ },
+ ),
+ ),
+ );
+ const controller = new AbortController();
+ const relayAbort = (): void => controller.abort(signal?.reason);
+ if (signal instanceof AbortSignal) {
+ if (signal.aborted) relayAbort();
+ else signal.addEventListener("abort", relayAbort, { once: true });
+ }
+ const operationSignal =
+ signal === undefined || signal instanceof AbortSignal
+ ? controller.signal
+ : signal;
+ const running = this.#observeProvider(
+ operation,
+ profile,
+ path,
+ parameters,
+ operationSignal,
+ );
+ const tracked = running.finally(() => {
+ signal?.removeEventListener("abort", relayAbort);
+ this.#activeOperations.delete(controller);
+ });
+ this.#activeOperations.set(controller, tracked);
+ return tracked;
+ }
+
+ async #observeProvider(
+ operation: ReverseEngineeringOperation,
+ profile: ReverseEngineeringProviderCommand,
+ path: string | undefined,
+ parameters: Readonly>,
+ signal?: AbortSignal,
+ ): Promise> {
+ const { executable: command, arguments: args } = profile;
+ if (signal?.aborted) return err(new AnalysisCancelledError(operation));
+ const cleanup = await this.retryCleanup(operation);
+ if (!cleanup.ok) return cleanup;
+ const absolutePath = path === undefined ? undefined : resolve(path);
+ let digest: string | undefined;
+ try {
+ if (absolutePath !== undefined)
+ digest = await this.#digest(absolutePath, signal, this.#stat);
+ } catch (cause) {
+ const code = (cause as NodeJS.ErrnoException).code;
+ if (signal?.aborted)
+ return err(new AnalysisCancelledError(operation, { cause }));
+ if (code === "EACCES" || code === "EPERM") {
+ const deniedPath = absolutePath === undefined ? command : absolutePath;
+ return err(
+ new AnalysisAccessDeniedError(operation, deniedPath, code, {
+ cause,
+ }),
+ );
+ }
+ if (code === "ETIMEDOUT")
+ return err(
+ new AnalysisTimeoutError(operation, DEFAULT_TIMEOUT_MS, { cause }),
+ );
+ return err(new AnalysisInputError(operation, { cause }));
+ }
+ let stdout = "";
+ let stderr = "";
+ let stdoutBytes = 0;
+ let stderrBytes = 0;
+ let exitCode: number | string | null = 0;
+ let signalName: string | null = null;
+ let outputTruncated = false;
+ let stdoutInvalidUtf8 = false;
+ let stderrInvalidUtf8 = false;
+ let cleanupIncomplete = false;
+ let cleanupOnlyFailure = false;
+ let completionStatus: "complete" | "unknown" = "complete";
+ let failure: AnalysisError | undefined;
+ let cleanupOwner: ProviderProcessSupervisor | undefined;
+ let earlyFailure: AnalysisError | undefined;
+ try {
+ const output = await this.#run(command, args, {
+ env: {
+ ...this.#environment,
+ ...profile.environmentOverrides,
+ },
+ signal,
+ timeout: DEFAULT_TIMEOUT_MS,
+ stopSignal: "SIGTERM",
+ maxBuffer: MAX_COMBINED_OUTPUT_BYTES,
+ });
+ stdout = output.stdout;
+ stderr = output.stderr;
+ stdoutBytes = output.stdoutBytes ?? Buffer.byteLength(stdout);
+ stderrBytes = output.stderrBytes ?? Buffer.byteLength(stderr);
+ stdoutInvalidUtf8 = output.stdoutInvalidUtf8 === true;
+ stderrInvalidUtf8 = output.stderrInvalidUtf8 === true;
+ } catch (cause) {
+ cleanupOwner =
+ cause instanceof OwnedOutputFailure ? cause.cleanupOwner : undefined;
+ if (
+ cause instanceof OwnedOutputFailure &&
+ cause.capabilityError !== undefined
+ )
+ earlyFailure = new AnalysisCapabilityUnavailableError(
+ cause.capabilityError.providerId,
+ cause.capabilityError.operation,
+ cause.capabilityError.reason,
+ {
+ cause: cause.capabilityError,
+ ...(cause.capabilityError.diagnostics === undefined
+ ? {}
+ : { diagnostics: cause.capabilityError.diagnostics }),
+ ...(cause.capabilityError.capturedOutput === undefined
+ ? {}
+ : { capturedOutput: cause.capabilityError.capturedOutput }),
+ ...(cause.capabilityError.partialObservation === undefined
+ ? {}
+ : {
+ partialObservation: cause.capabilityError.partialObservation,
+ }),
+ ...(cause.capabilityError.userMessage === undefined
+ ? {}
+ : { userMessage: cause.capabilityError.userMessage }),
+ retryable: cause.capabilityError.retryable,
+ ...ownedCleanupOptions(cause),
+ },
+ );
+ const capture = execFileOutputFailure(cause);
+ const code =
+ capture?.code ?? (cause as NodeJS.ErrnoException).code ?? undefined;
+ if (earlyFailure === undefined && code === "E2BIG")
+ earlyFailure = new AnalysisResourceConstraintError(
+ operation,
+ "transport",
+ "The host rejected the provider command because its argument list exceeds the process launch limit.",
+ {
+ argument_bytes: args.reduce(
+ (bytes, argument, index) =>
+ bytes + Buffer.byteLength(argument) + (index === 0 ? 0 : 1),
+ 0,
+ ),
+ },
+ { cause, ...ownedCleanupOptions(cause) },
+ );
+ if (
+ earlyFailure === undefined &&
+ (capture === undefined ||
+ code === "ENOENT" ||
+ code === "ERR_PROVIDER_LAUNCH")
+ ) {
+ if (signal?.aborted || code === "ABORT_ERR")
+ earlyFailure = new AnalysisCancelledError(operation, {
+ cause,
+ ...ownedCleanupOptions(cause),
+ });
+ else if (code === "ETIMEDOUT")
+ earlyFailure = new AnalysisTimeoutError(
+ operation,
+ DEFAULT_TIMEOUT_MS,
+ {
+ cause,
+ ...ownedCleanupOptions(cause),
+ },
+ );
+ else if (code === "EACCES" || code === "EPERM")
+ earlyFailure = new AnalysisAccessDeniedError(
+ operation,
+ command,
+ code,
+ {
+ cause,
+ ...ownedCleanupOptions(cause),
+ },
+ );
+ else
+ earlyFailure = new AnalysisCapabilityUnavailableError(
+ operation === "inspect_with_objdump" ? "objdump" : "rizin",
+ operation,
+ `Unable to run configured command (${String(code ?? "unknown error")})`,
+ { cause, ...ownedCleanupOptions(cause) },
+ );
+ }
+ if (earlyFailure === undefined && capture !== undefined) {
+ stdout = capture.stdout;
+ stderr = capture.stderr;
+ stdoutBytes = capture.stdoutBytes ?? Buffer.byteLength(stdout);
+ stderrBytes = capture.stderrBytes ?? Buffer.byteLength(stderr);
+ exitCode = capture.code;
+ signalName = capture.signal;
+ outputTruncated = capture.outputTruncated;
+ if (capture.collectionFailure) {
+ exitCode = null;
+ completionStatus = "unknown";
+ }
+ const capturedOutput = {
+ stdout,
+ stderr,
+ truncated: outputTruncated,
+ stdout_bytes: stdoutBytes,
+ stderr_bytes: stderrBytes,
+ exit_code:
+ !capture.collectionFailure && typeof capture.code === "number"
+ ? capture.code
+ : null,
+ signal: capture.signal,
+ };
+ if (signal?.aborted || code === "ABORT_ERR")
+ failure = new AnalysisCancelledError(operation, {
+ cause,
+ capturedOutput,
+ ...ownedCleanupOptions(cause),
+ });
+ else if (code === "ETIMEDOUT")
+ failure = new AnalysisTimeoutError(operation, DEFAULT_TIMEOUT_MS, {
+ cause,
+ capturedOutput,
+ ...ownedCleanupOptions(cause),
+ });
+ else if (code === "EACCES" || code === "EPERM")
+ failure = new AnalysisAccessDeniedError(operation, command, code, {
+ cause,
+ capturedOutput,
+ ...ownedCleanupOptions(cause),
+ });
+ if (
+ cause instanceof OwnedOutputFailure &&
+ cause.cleanup !== undefined
+ ) {
+ cleanupIncomplete = true;
+ if (failure === undefined) {
+ cleanupOnlyFailure =
+ !capture.collectionFailure &&
+ !capture.outputTruncated &&
+ !capture.killed &&
+ capture.signal === null &&
+ typeof capture.code === "number" &&
+ !signal?.aborted;
+ failure = new AnalysisOutputError(
+ operation,
+ "Provider process cleanup remains incomplete.",
+ {
+ cause,
+ capturedOutput,
+ cleanup: cause.cleanup,
+ cleanupOnly: cleanupOnlyFailure,
+ },
+ );
+ }
+ }
+ if (
+ failure === undefined &&
+ cause instanceof OwnedOutputFailure &&
+ cause.collectionFailure
+ )
+ failure = new AnalysisOutputError(
+ operation,
+ "Provider output collection failed.",
+ { cause, capturedOutput },
+ );
+ if (
+ capture.killed ||
+ capture.signal !== null ||
+ typeof capture.code !== "number"
+ ) {
+ exitCode = null;
+ completionStatus = "unknown";
+ }
+ if (cause instanceof OwnedOutputFailure) {
+ stdoutInvalidUtf8 = cause.stdoutInvalidUtf8;
+ stderrInvalidUtf8 = cause.stderrInvalidUtf8;
+ }
+ }
+ }
+ if (earlyFailure !== undefined)
+ return err(this.#registerCleanupFailure(earlyFailure, cleanupOwner));
+ if (failure !== undefined && cleanupOwner !== undefined)
+ failure = this.#registerCleanupFailure(failure, cleanupOwner);
+ let digestAfter: string | null = null;
+ if (absolutePath === undefined) {
+ digestAfter = null;
+ } else if (cleanupIncomplete) {
+ if (!cleanupOnlyFailure) completionStatus = "unknown";
+ } else {
+ try {
+ digestAfter = await this.#digest(absolutePath, signal, this.#stat);
+ } catch {
+ // Preserve the provider's collected observations if the artifact vanished or became unreadable.
+ }
+ }
+ if (signal?.aborted && failure === undefined) {
+ failure = new AnalysisCancelledError(operation, {
+ capturedOutput: {
+ stdout,
+ stderr,
+ truncated: outputTruncated,
+ stdout_bytes: stdoutBytes,
+ stderr_bytes: stderrBytes,
+ exit_code: typeof exitCode === "number" ? exitCode : null,
+ signal: signalName,
+ },
+ });
+ exitCode = null;
+ completionStatus = "unknown";
+ }
+ const artifactChanged =
+ absolutePath === undefined
+ ? null
+ : digestAfter === null || digest === undefined
+ ? null
+ : digestAfter !== digest;
+ const result = {
+ stdout,
+ stderr,
+ exit_code: exitCode,
+ signal: signalName,
+ output_truncated: outputTruncated,
+ completion_status: completionStatus,
+ artifact_sha256_after: absolutePath === undefined ? null : digestAfter,
+ artifact_changed: absolutePath === undefined ? null : artifactChanged,
+ };
+ const limitations = [
+ ...(absolutePath === undefined
+ ? [
+ "The objdump version is not reported; the output lists BFD targets advertised by the installed tool and does not establish support for the selected artifact.",
+ ]
+ : [
+ "The executable version and supported formats are reported as unknown because no reliable version probe was performed.",
+ ]),
+ "A non-zero process exit is preserved as an observed command result.",
+ ...(operation === "inspect_with_objdump" &&
+ parameters.follow_debug_links === true
+ ? [
+ "DWARF output may include local debug-link files whose identities are not bound to Evidence.",
+ ]
+ : []),
+ ...(operation === "execute_rizin_command"
+ ? [
+ "Rizin commands and retained plugins may read files or access network resources outside the selected artifact; those external inputs are not bound to Evidence.",
+ ]
+ : []),
+ ...(stdoutInvalidUtf8 || stderrInvalidUtf8
+ ? [
+ `Captured ${[
+ ...(stdoutInvalidUtf8 ? ["stdout"] : []),
+ ...(stderrInvalidUtf8 ? ["stderr"] : []),
+ ].join(
+ " and ",
+ )} bytes contain invalid or incomplete UTF-8; replacement characters in the text capture do not preserve the original bytes.`,
+ ]
+ : []),
+ ...(outputTruncated
+ ? [
+ "The provider command ended before completion could be confirmed; captured output is partial.",
+ ]
+ : []),
+ ...(signalName === null
+ ? []
+ : [
+ `The provider process terminated by signal ${signalName}; captured output may be partial and command completion is unknown.`,
+ ]),
+ ...(failure === undefined ||
+ (failure instanceof AnalysisOutputError && failure.cleanupOnly)
+ ? []
+ : [
+ `Command completion is unknown because the operation failed with ${failure._tag}: ${failure.message}`,
+ ]),
+ ...(absolutePath === undefined
+ ? [
+ "This output lists BFD targets advertised by the installed objdump and is independent of the selected artifact.",
+ ]
+ : [
+ "When both are available, pre-run and post-run digests detect artifact changes across the provider call but cannot prove the exact bytes consumed during the call.",
+ ]),
+ ...(artifactChanged === true
+ ? [
+ "The provider command may have changed the artifact while analysis was running; pre-run and post-run digests are both reported.",
+ ]
+ : []),
+ ...(absolutePath !== undefined && digestAfter === null
+ ? [
+ cleanupIncomplete
+ ? "The post-run artifact digest was not captured because owned provider cleanup was incomplete at observation time; artifact changes during the command remain unquantified."
+ : signal?.aborted
+ ? "The post-run artifact digest is unknown because cancellation interrupted the digest."
+ : "The post-run artifact digest is unknown because the artifact could not be read after the provider call.",
+ ]
+ : []),
+ ];
+ const evidence = createEvidence(
+ absolutePath === undefined || digest === undefined
+ ? undefined
+ : { path: absolutePath, sha256: digest, format: "unknown" },
+ profile.identity,
+ {
+ predicateType: "rea.reverse-engineering.command-observation",
+ operation,
+ parameters: {
+ ...(absolutePath === undefined
+ ? { operation: "architectures" }
+ : parameters),
+ provider_executable: command,
+ command_arguments: [...args],
+ },
+ rawResult: result,
+ result,
+ limitations,
+ ...(absolutePath === undefined
+ ? {}
+ : {
+ locations: [
+ { kind: "artifact-path" as const, path: absolutePath },
+ ],
+ }),
+ },
+ );
+ return failure === undefined
+ ? ok(evidence)
+ : err(failure.retainPartialObservation(evidence));
+ }
+
+ #registerCleanupFailure(
+ failure: AnalysisError,
+ cleanupOwner: ProviderProcessSupervisor | undefined,
+ ): AnalysisError {
+ if (cleanupOwner !== undefined && failure.cleanupIncomplete)
+ this.#pendingCleanup.get(cleanupOwner)?.failures.add(failure);
+ return failure;
+ }
+}
+
+interface OwnedOutputFailureOptions {
+ readonly stdout: string;
+ readonly stderr: string;
+ readonly code: number | string | null;
+ readonly signal: string | null;
+ readonly outputTruncated: boolean;
+ readonly stdoutInvalidUtf8: boolean;
+ readonly stderrInvalidUtf8: boolean;
+ readonly stdoutBytes: number;
+ readonly stderrBytes: number;
+ readonly capabilityError?: AnalysisCapabilityUnavailableError;
+ readonly collectionFailure?: boolean;
+ readonly cleanup?: AnalysisCleanupObservation;
+ readonly cleanupOwner?: ProviderProcessSupervisor;
+ readonly cause?: unknown;
+}
+
+class OwnedOutputFailure extends Error {
+ readonly stdout: string;
+ readonly stderr: string;
+ readonly code: number | string | null;
+ readonly signal: string | null;
+ readonly outputTruncated: boolean;
+ readonly stdoutInvalidUtf8: boolean;
+ readonly stderrInvalidUtf8: boolean;
+ readonly stdoutBytes: number;
+ readonly stderrBytes: number;
+ readonly capabilityError: AnalysisCapabilityUnavailableError | undefined;
+ readonly collectionFailure: boolean;
+ readonly cleanup: AnalysisCleanupObservation | undefined;
+ readonly cleanupOwner: ProviderProcessSupervisor | undefined;
+
+ constructor(options: OwnedOutputFailureOptions) {
+ super("Owned provider command did not produce a complete result.", {
+ cause: options.cause,
+ });
+ this.stdout = options.stdout;
+ this.stderr = options.stderr;
+ this.code = options.code;
+ this.signal = options.signal;
+ this.outputTruncated = options.outputTruncated;
+ this.stdoutInvalidUtf8 = options.stdoutInvalidUtf8;
+ this.stderrInvalidUtf8 = options.stderrInvalidUtf8;
+ this.stdoutBytes = options.stdoutBytes;
+ this.stderrBytes = options.stderrBytes;
+ this.capabilityError = options.capabilityError;
+ this.collectionFailure = options.collectionFailure ?? false;
+ this.cleanup = options.cleanup;
+ this.cleanupOwner = options.cleanupOwner;
+ }
+}
+
+const runOwnedCommandOutput =
+ (
+ runOwned: typeof runOwnedCommand,
+ onCleanupOwner: (
+ owner: ProviderProcessSupervisor,
+ cleanup: AnalysisCleanupObservation,
+ ) => void,
+ ): ReverseEngineeringCommandRunner =>
+ async (command, args, options = {}) => {
+ const spawnEnv = { ...options.env };
+ try {
+ const snapshot = await runOwned(
+ {
+ command,
+ arguments: [...args],
+ runId: randomUUID(),
+ expectedCommand: null,
+ env: spawnEnv,
+ hostEnvironment: {},
+ sweepTokenOwnedProcesses: true,
+ },
+ {
+ timeoutMs: DEFAULT_TIMEOUT_MS,
+ diagnosticBytes: MAX_COMBINED_OUTPUT_BYTES,
+ acceptNonZeroExit: true,
+ },
+ { signal: options.signal },
+ );
+ if (snapshot.signal !== null && snapshot.signal !== undefined)
+ throw new OwnedOutputFailure({
+ stdout: snapshot.stdout.text,
+ stderr: snapshot.stderr.text,
+ code: snapshot.exitCode ?? null,
+ signal: snapshot.signal,
+ outputTruncated: snapshot.diagnosticTruncated === true,
+ stdoutInvalidUtf8: snapshot.stdout.invalidUtf8 === true,
+ stderrInvalidUtf8: snapshot.stderr.invalidUtf8 === true,
+ stdoutBytes: snapshot.stdout.bytes,
+ stderrBytes: snapshot.stderr.bytes,
+ });
+ if (snapshot.exitCode !== 0)
+ throw new OwnedOutputFailure({
+ stdout: snapshot.stdout.text,
+ stderr: snapshot.stderr.text,
+ code: snapshot.exitCode ?? null,
+ signal: null,
+ outputTruncated: snapshot.diagnosticTruncated === true,
+ stdoutInvalidUtf8: snapshot.stdout.invalidUtf8 === true,
+ stderrInvalidUtf8: snapshot.stderr.invalidUtf8 === true,
+ stdoutBytes: snapshot.stdout.bytes,
+ stderrBytes: snapshot.stderr.bytes,
+ });
+ return {
+ stdout: snapshot.stdout.text,
+ stderr: snapshot.stderr.text,
+ stdoutInvalidUtf8: snapshot.stdout.invalidUtf8 === true,
+ stderrInvalidUtf8: snapshot.stderr.invalidUtf8 === true,
+ stdoutBytes: snapshot.stdout.bytes,
+ stderrBytes: snapshot.stderr.bytes,
+ };
+ } catch (cause) {
+ if (!(cause instanceof OwnedCommandFailure)) throw cause;
+ if (cause.cleanupOwner !== undefined && cause.cleanupFailure !== null)
+ onCleanupOwner(cause.cleanupOwner, {
+ reason: cause.cleanupFailure,
+ resources: cause.resources,
+ });
+ const snapshot = cause.snapshot;
+ const launchErrorCode =
+ cause.cause instanceof Error
+ ? Reflect.get(cause.cause, "code")
+ : undefined;
+ const code =
+ cause.reason === "cancelled"
+ ? "ABORT_ERR"
+ : cause.reason === "timeout"
+ ? "ETIMEDOUT"
+ : cause.reason === "output-limit"
+ ? "ERR_CHILD_PROCESS_STDIO_MAXBUFFER"
+ : launchErrorCode === "E2BIG"
+ ? "E2BIG"
+ : snapshot === null
+ ? "ERR_PROVIDER_LAUNCH"
+ : (snapshot.exitCode ?? null);
+ throw new OwnedOutputFailure({
+ stdout: snapshot?.stdout.text ?? "",
+ stderr: snapshot?.stderr.text ?? "",
+ code,
+ signal: snapshot?.signal ?? null,
+ outputTruncated: snapshot?.diagnosticTruncated === true,
+ stdoutInvalidUtf8: snapshot?.stdout.invalidUtf8 === true,
+ stderrInvalidUtf8: snapshot?.stderr.invalidUtf8 === true,
+ stdoutBytes: snapshot?.stdout.bytes ?? 0,
+ stderrBytes: snapshot?.stderr.bytes ?? 0,
+ ...(cause.cause instanceof AnalysisCapabilityUnavailableError
+ ? { capabilityError: cause.cause }
+ : {}),
+ ...(cause.collectionFailure ? { collectionFailure: true } : {}),
+ ...(cause.cleanupFailure === null
+ ? {}
+ : {
+ cleanup: {
+ reason: cause.cleanupFailure,
+ resources: cause.resources,
+ },
+ }),
+ ...(cause.cleanupOwner === undefined
+ ? {}
+ : { cleanupOwner: cause.cleanupOwner }),
+ cause,
+ });
+ }
+ };
+
+const ownedCleanupOptions = (
+ cause: unknown,
+): { readonly cleanup?: AnalysisCleanupObservation } =>
+ cause instanceof OwnedOutputFailure && cause.cleanup !== undefined
+ ? { cleanup: cause.cleanup }
+ : {};
+
+const digestFile = async (
+ path: string,
+ signal?: AbortSignal,
+ stat: (path: string) => Promise = statPath,
+): Promise => {
+ const info = await statWithDeadline(path, signal, stat);
+ if (!info.isFile())
+ throw Object.assign(new Error("Artifact digest requires a regular file"), {
+ code: "EINVAL",
+ });
+ return new Promise((resolveDigest, rejectDigest) => {
+ const hash = createHash("sha256");
+ const stream = createReadStream(path);
+ const timer = setTimeout(
+ () =>
+ stream.destroy(
+ Object.assign(new Error("Artifact digest timed out"), {
+ code: "ETIMEDOUT",
+ }),
+ ),
+ DEFAULT_TIMEOUT_MS,
+ );
+ const abort = (): void => {
+ stream.destroy(new AnalysisCancelledError("artifact_digest"));
+ };
+ const cleanup = (): void => {
+ clearTimeout(timer);
+ signal?.removeEventListener("abort", abort);
+ };
+ if (signal?.aborted) {
+ cleanup();
+ stream.destroy();
+ rejectDigest(new AnalysisCancelledError("artifact_digest"));
+ return;
+ }
+ signal?.addEventListener("abort", abort, { once: true });
+ stream.on("data", (chunk: Buffer | string) => hash.update(chunk));
+ stream.once("error", (cause: Error) => {
+ cleanup();
+ rejectDigest(cause);
+ });
+ stream.once("end", () => {
+ cleanup();
+ resolveDigest(hash.digest("hex"));
+ });
+ });
+};
+
+const statWithDeadline = (
+ path: string,
+ signal: AbortSignal | undefined,
+ stat: (path: string) => Promise,
+): Promise => {
+ if (signal?.aborted)
+ return Promise.reject(new AnalysisCancelledError("artifact_digest"));
+ return new Promise((resolveStat, rejectStat) => {
+ let settled = false;
+ const finish = (cause?: unknown, value?: Stats): void => {
+ if (settled) return;
+ settled = true;
+ clearTimeout(timer);
+ signal?.removeEventListener("abort", abort);
+ if (cause !== undefined) rejectStat(cause);
+ else resolveStat(value!);
+ };
+ const abort = (): void =>
+ finish(new AnalysisCancelledError("artifact_digest"));
+ const timer = setTimeout(
+ () =>
+ finish(
+ Object.assign(new Error("Artifact stat timed out"), {
+ code: "ETIMEDOUT",
+ }),
+ ),
+ DEFAULT_TIMEOUT_MS,
+ );
+ signal?.addEventListener("abort", abort, { once: true });
+ if (signal?.aborted) {
+ abort();
+ return;
+ }
+ // Promise handlers stay attached after timeout/cancellation so a delayed
+ // stat rejection is observed and cannot become an unhandled rejection.
+ try {
+ void stat(path).then(
+ (info) => finish(undefined, info),
+ (cause: unknown) => finish(cause),
+ );
+ } catch (cause) {
+ finish(cause);
+ }
+ });
+};
diff --git a/src/cli.ts b/src/cli.ts
index d5b009241..a12d1e327 100644
--- a/src/cli.ts
+++ b/src/cli.ts
@@ -49,6 +49,8 @@ import {
streamCliCommandResults,
type CliResultOutput,
} from "./cli/cliResultOutput.js";
+import { registerReverseEngineeringCommands } from "./cli/reverseEngineeringCommands.js";
+import { registerCutterCommands } from "./cli/cutterCommands.js";
/** Describe CLI settings that are invalid before any command runs, as MCP does. */
export const cliConfigurationError = (
@@ -95,6 +97,8 @@ export const createCli = (
});
registerSetupCommands(cli, logger, environment);
+ registerReverseEngineeringCommands(cli, logger, environment);
+ registerCutterCommands(cli, logger, environment);
registerCoreAnalysisCommands(cli, logger, analysis.runDirectAnalysis);
registerUtilityCommands(cli, logger, environment, analysis);
registerWasmCommands(cli, logger, environment);
diff --git a/src/cli/cutterCommands.ts b/src/cli/cutterCommands.ts
new file mode 100644
index 000000000..2b8ce574b
--- /dev/null
+++ b/src/cli/cutterCommands.ts
@@ -0,0 +1,69 @@
+import { z } from "incur";
+import type { Logger } from "pino";
+
+import { CutterBridgeClient } from "../cutter/CutterBridgeClient.js";
+import { CutterBridgeService } from "../cutter/CutterBridgeService.js";
+import { CLI_COMMANDS } from "../cliCommandNames.js";
+import { logCliCommand } from "../cliLogging.js";
+import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
+import { withCommandCancellation } from "./commandCancellation.js";
+import type { CliInstance } from "./types.js";
+
+/** Register commands that control an already-running Cutter instance. */
+export const registerCutterCommands = (
+ cli: CliInstance,
+ logger: Logger,
+ environment: Readonly,
+): void => {
+ const cutter = new CutterBridgeService(new CutterBridgeClient(environment));
+
+ cli.command(CLI_COMMANDS.listCutterSessions, {
+ description:
+ "List live Cutter instances connected through the REA Python plugin bridge",
+ args: z.object({}),
+ run: () =>
+ logCliCommand(logger, CLI_COMMANDS.listCutterSessions, () =>
+ cutter.listSessions(),
+ ),
+ });
+ cli.command(CLI_COMMANDS.cutterCommand, {
+ description: "Execute one Rizin command in a selected live Cutter instance",
+ args: z.object({
+ sessionId: z
+ .string()
+ .uuid()
+ .describe("Session ID copied from list-cutter-sessions."),
+ expectedGeneration: z.coerce
+ .number()
+ .int()
+ .nonnegative()
+ .describe("Session generation returned by list-cutter-sessions."),
+ command: z
+ .string()
+ .min(1)
+ .describe("One unrestricted Rizin command to run in Cutter.")
+ .refine((value) => !/[\r\n]/u.test(value)),
+ }),
+ options: z.object({
+ json: z
+ .boolean()
+ .default(false)
+ .describe("Return command-specific JSON output when available."),
+ }),
+ run: ({ args, options }) =>
+ withCommandCancellation((signal) =>
+ logCliCommand(logger, CLI_COMMANDS.cutterCommand, async () => {
+ const result = await cutter.execute(
+ {
+ session_id: args.sessionId,
+ expected_generation: args.expectedGeneration,
+ command: args.command,
+ json: options.json,
+ },
+ { signal },
+ );
+ return result.ok ? result.value : projectAnalysisError(result.error);
+ }),
+ ),
+ });
+};
diff --git a/src/cli/reverseEngineeringCommands.ts b/src/cli/reverseEngineeringCommands.ts
new file mode 100644
index 000000000..1feb9184d
--- /dev/null
+++ b/src/cli/reverseEngineeringCommands.ts
@@ -0,0 +1,367 @@
+import { z } from "incur";
+import type { Logger } from "pino";
+
+import { createReverseEngineeringService } from "../composition/reverseEngineering.js";
+import { AnalysisError } from "../domain/analysisErrorBase.js";
+import {
+ AnalysisInputError,
+ AnalysisOutputError,
+} from "../domain/analysisErrorCore.js";
+import { evidenceSchema, type Evidence } from "../domain/evidence.js";
+import type { Result } from "../domain/result.js";
+import { err } from "../domain/result.js";
+import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
+import { logCliCommand } from "../cliLogging.js";
+import type { CliInstance } from "./types.js";
+import { withCommandCancellation } from "./commandCancellation.js";
+import { CLI_COMMANDS } from "../cliCommandNames.js";
+import type { ReverseEngineeringService } from "../application/reverse/ReverseEngineeringService.js";
+import {
+ GdbSessionManager,
+ gdbOneShotCommandMayInvalidateDetachProof,
+} from "../gdb/GdbSessionManager.js";
+import { RizinDebugSessionManager } from "../rizin/RizinDebugSessionManager.js";
+
+const executeWithCleanup = async (
+ service: ReverseEngineeringService,
+ execute: () => Promise>,
+): Promise> => {
+ let result: Result;
+ try {
+ result = await execute();
+ } catch (cause) {
+ await service.close().catch(() => undefined);
+ throw cause;
+ }
+ const hadIncompleteCleanup = !result.ok && result.error.cleanupIncomplete;
+ try {
+ await service.close();
+ } catch (cause) {
+ if (result.ok) return err(asCleanupError(cause));
+ const cleanup = cause instanceof AnalysisError ? cause.cleanup : undefined;
+ const error = new AnalysisOutputError(
+ "reverse_engineering_cleanup",
+ "Provider cleanup remains incomplete after the command failed.",
+ {
+ cause: result.error,
+ ...(cleanup === undefined ? {} : { cleanup }),
+ ...(result.error.capturedOutput === undefined
+ ? {}
+ : { capturedOutput: result.error.capturedOutput }),
+ ...(result.error.partialObservation === undefined
+ ? {}
+ : { partialObservation: result.error.partialObservation }),
+ },
+ );
+ return err(error);
+ }
+ if (!result.ok && hadIncompleteCleanup && !result.error.cleanupIncomplete) {
+ const evidence = cleanupOnlyCompleteEvidence(result.error);
+ if (evidence !== undefined) return { ok: true, value: evidence };
+ }
+ return result;
+};
+
+const cleanupOnlyCompleteEvidence = (
+ error: AnalysisError,
+): Evidence | undefined => {
+ if (
+ !(error instanceof AnalysisOutputError) ||
+ !error.cleanupOnly ||
+ error.partialObservation === undefined
+ )
+ return undefined;
+ const parsed = evidenceSchema.safeParse(error.partialObservation);
+ if (!parsed.success) return undefined;
+ const normalized = parsed.data.normalized_result;
+ return typeof normalized === "object" &&
+ normalized !== null &&
+ !Array.isArray(normalized) &&
+ normalized.completion_status === "complete"
+ ? parsed.data
+ : undefined;
+};
+
+const asCleanupError = (cause: unknown): AnalysisError =>
+ cause instanceof AnalysisError
+ ? cause
+ : new AnalysisOutputError(
+ "reverse_engineering_cleanup",
+ "Provider process cleanup failed.",
+ { cause },
+ );
+
+/** Add one-shot reverse-engineering commands using the shared service layer. */
+export const registerReverseEngineeringCommands = (
+ cli: CliInstance,
+ logger: Logger,
+ environment: Readonly,
+ createGdbManager: (
+ environment: Readonly,
+ ) => GdbSessionManager = (providerEnvironment) =>
+ new GdbSessionManager({ environment: providerEnvironment }),
+ createService: typeof createReverseEngineeringService = createReverseEngineeringService,
+): void => {
+ const service = createService(environment);
+ cli.command(CLI_COMMANDS.inspectWithObjdump, {
+ description:
+ "Inspect a local artifact with GNU objdump and retain its raw output as Evidence",
+ args: z.object({
+ path: z
+ .string()
+ .min(1)
+ .describe("Path to the local artifact to inspect."),
+ operation: z
+ .enum([
+ "file_headers",
+ "section_headers",
+ "symbols",
+ "relocations",
+ "dwarf",
+ "disassemble",
+ "disassemble_all",
+ ])
+ .describe("GNU objdump inspection operation to run."),
+ }),
+ options: z.object({
+ followDebugLinks: z
+ .boolean()
+ .default(false)
+ .describe(
+ "Follow local DWARF debug links. External debug-file identity is not bound to Evidence.",
+ ),
+ }),
+ run: ({ args, options }) =>
+ withCommandCancellation((signal) =>
+ logCliCommand(logger, CLI_COMMANDS.inspectWithObjdump, async () => {
+ const result = await executeWithCleanup(service, () =>
+ service.inspectWithObjdump(
+ {
+ ...args,
+ follow_debug_links: options.followDebugLinks,
+ },
+ { signal },
+ ),
+ );
+ return result.ok ? result.value : projectAnalysisError(result.error);
+ }),
+ ),
+ });
+ cli.command(CLI_COMMANDS.listObjdumpArchitectures, {
+ description: "List BFD targets advertised by the installed GNU objdump",
+ args: z.object({}),
+ run: () =>
+ withCommandCancellation((signal) =>
+ logCliCommand(
+ logger,
+ CLI_COMMANDS.listObjdumpArchitectures,
+ async () => {
+ const result = await executeWithCleanup(service, () =>
+ service.inspectWithObjdump(
+ { operation: "architectures" },
+ { signal },
+ ),
+ );
+ return result.ok
+ ? result.value
+ : projectAnalysisError(result.error);
+ },
+ ),
+ ),
+ });
+ cli.command(CLI_COMMANDS.executeRizinCommand, {
+ description: "Execute one Rizin command against a local artifact",
+ args: z.object({
+ path: z
+ .string()
+ .min(1)
+ .describe("Path to the local artifact to analyze."),
+ command: z.string().min(1).describe("One caller-selected Rizin command."),
+ }),
+ run: ({ args }) =>
+ withCommandCancellation((signal) =>
+ logCliCommand(logger, CLI_COMMANDS.executeRizinCommand, async () => {
+ const result = await executeWithCleanup(service, () =>
+ service.executeRizinCommand(args, { signal }),
+ );
+ return result.ok ? result.value : projectAnalysisError(result.error);
+ }),
+ ),
+ });
+ cli.command(CLI_COMMANDS.debugWithGdb, {
+ description:
+ "Run one unrestricted GDB console command in an ephemeral MI session; provide an inferior TTY so a live target can be safely detached",
+ args: z.object({
+ command: z
+ .string()
+ .min(1)
+ .describe("One unrestricted GDB console command."),
+ }),
+ options: z.object({
+ inferiorTty: z
+ .string()
+ .min(1)
+ .describe(
+ "Caller-selected durable TTY for output from a live GDB inferior.",
+ ),
+ }),
+ run: ({ args, options }) =>
+ withCommandCancellation((signal) =>
+ logCliCommand(logger, CLI_COMMANDS.debugWithGdb, async () => {
+ if (gdbOneShotCommandMayInvalidateDetachProof(args.command))
+ return projectAnalysisError(
+ new AnalysisInputError("gdb_console", undefined, [
+ {
+ path: ["command"],
+ reason: "invalid_value",
+ message:
+ "This command can invalidate the verified inferior output route; use the persistent GDB session workflow.",
+ },
+ ]),
+ );
+ const manager = createGdbManager(environment);
+ let sessionId: string | undefined;
+ try {
+ const started = await manager.start(signal, {
+ inferiorTty: options.inferiorTty,
+ });
+ if (!started.ok) return projectAnalysisError(started.error);
+ sessionId = started.value.session_id;
+ const executed = await manager.execute(
+ sessionId,
+ args.command,
+ signal,
+ );
+ return executed.ok
+ ? executed.value.evidence
+ : projectAnalysisError(executed.error);
+ } finally {
+ let cleanupFailed = false;
+ if (sessionId !== undefined) {
+ try {
+ const closed = await manager.close(sessionId);
+ cleanupFailed = !closed.ok;
+ if (!closed.ok)
+ logger.error(
+ {
+ command: CLI_COMMANDS.debugWithGdb,
+ cleanup: projectAnalysisError(closed.error),
+ },
+ "GDB cleanup failed after collecting command output",
+ );
+ } catch (cause) {
+ cleanupFailed = true;
+ logger.error(
+ {
+ command: CLI_COMMANDS.debugWithGdb,
+ cleanup:
+ cause instanceof Error
+ ? cause.message
+ : "Unknown cleanup failure",
+ },
+ "GDB cleanup failed after collecting command output",
+ );
+ }
+ }
+ try {
+ await manager.closeAll();
+ cleanupFailed = false;
+ } catch (cause) {
+ cleanupFailed = true;
+ logger.error(
+ {
+ command: CLI_COMMANDS.debugWithGdb,
+ cleanup:
+ cause instanceof Error
+ ? cause.message
+ : "Unknown cleanup failure",
+ },
+ "GDB fallback cleanup failed",
+ );
+ }
+ if (cleanupFailed) process.exitCode = 1;
+ }
+ }),
+ ),
+ });
+ cli.command(CLI_COMMANDS.debugWithRizin, {
+ description:
+ "Run one unrestricted Rizin debugger command in an ephemeral session",
+ args: z.object({
+ path: z.string().min(1).describe("Path to the local artifact to debug."),
+ command: z
+ .string()
+ .min(1)
+ .describe("One unrestricted Rizin debugger command.")
+ .refine((value) => !/[\r\n]/u.test(value)),
+ }),
+ options: z.object({
+ inferiorTty: z
+ .string()
+ .min(1)
+ .describe("Caller-selected Linux TTY for inferior stdout and stderr."),
+ backend: z
+ .string()
+ .min(1)
+ .optional()
+ .describe("Optional Rizin IO debugger backend name."),
+ }),
+ run: ({ args, options }) =>
+ withCommandCancellation((signal) =>
+ logCliCommand(logger, CLI_COMMANDS.debugWithRizin, async () => {
+ const manager = new RizinDebugSessionManager(environment);
+ let sessionId: string | undefined;
+ try {
+ const started = await manager.start(
+ {
+ ...args,
+ inferior_tty: options.inferiorTty,
+ ...(options.backend === undefined
+ ? {}
+ : { backend: options.backend }),
+ },
+ signal,
+ );
+ if (!started.ok) return projectAnalysisError(started.error);
+ sessionId = started.value.session_id;
+ const executed = await manager.execute(
+ started.value.session_id,
+ args.command,
+ signal,
+ );
+ return executed.ok
+ ? executed.value.evidence
+ : projectAnalysisError(executed.error);
+ } finally {
+ const closed =
+ sessionId === undefined
+ ? undefined
+ : await manager.close(sessionId);
+ if (closed !== undefined && !closed.ok) {
+ logger.error(
+ {
+ command: CLI_COMMANDS.debugWithRizin,
+ cleanup: projectAnalysisError(closed.error),
+ },
+ "Rizin cleanup failed after collecting command output",
+ );
+ process.exitCode = 1;
+ }
+ await manager.closeAll().catch((cause: unknown) => {
+ process.exitCode = 1;
+ logger.error(
+ {
+ command: CLI_COMMANDS.debugWithRizin,
+ cleanup:
+ cause instanceof Error
+ ? cause.message
+ : "Unknown cleanup failure",
+ },
+ "Rizin fallback cleanup failed",
+ );
+ });
+ }
+ }),
+ ),
+ });
+};
diff --git a/src/cliCommandNames.ts b/src/cliCommandNames.ts
index 70a3a4d37..798451158 100644
--- a/src/cliCommandNames.ts
+++ b/src/cliCommandNames.ts
@@ -49,6 +49,14 @@ export const CLI_COMMANDS = Object.freeze({
inspectManagedArtifact: "inspect-managed-artifact",
inspectRecordedCrash: "inspect-recorded-crash",
inspectBinaryLayout: "inspect-binary-layout",
+ listCutterSessions: "list-cutter-sessions",
+ cutterCommand: "cutter-command",
+ inspectWithObjdump: "inspect-with-objdump",
+ listObjdumpArchitectures: "list-objdump-architectures",
+ executeRizinCommand: "execute-rizin-command",
+ debugWithGdb: "debug-with-gdb",
+ debugWithRizin: "debug-with-rizin",
+
inspectGoBinary: "inspect-go-binary",
inspectRustBinary: "inspect-rust-binary",
inspectPeResources: "inspect-pe-resources",
@@ -164,6 +172,13 @@ export const CLI_COMMAND_TOOL_ALIASES = Object.freeze({
search: ["search_strings", "search_procedures"],
"capture-process": ["capture_process_scenario"],
"compare-bundles": ["compare_bundles"],
+ "list-cutter-sessions": ["list_cutter_sessions"],
+ "cutter-command": ["cutter_command"],
+ "inspect-with-objdump": ["inspect_with_objdump"],
+ "list-objdump-architectures": ["inspect_with_objdump"],
+ "execute-rizin-command": ["execute_rizin_command"],
+ "debug-with-gdb": ["gdb_console"],
+ "debug-with-rizin": ["rizin_debug_command"],
"list-frida-devices": ["list_frida_devices"],
"list-frida-processes": ["list_frida_processes"],
"instrument-with-frida": ["instrument_with_frida"],
@@ -224,6 +239,12 @@ export const MCP_TOOLS_WITHOUT_DEDICATED_CLI = Object.freeze([
"get_evidence_bundle",
"get_navigation_context",
"inspect_address_context",
+ "start_gdb_session",
+ "gdb_session_status",
+ "close_gdb_session",
+ "start_rizin_debug_session",
+ "rizin_debug_session_status",
+ "close_rizin_debug_session",
"start_frida_session",
"load_frida_script",
"resume_frida_session",
diff --git a/src/composition/resolveWindowsProviderExecutable.ts b/src/composition/resolveWindowsProviderExecutable.ts
new file mode 100644
index 000000000..da4079056
--- /dev/null
+++ b/src/composition/resolveWindowsProviderExecutable.ts
@@ -0,0 +1,77 @@
+import { existsSync, statSync } from "node:fs";
+import { win32 } from "node:path";
+
+export interface WindowsExecutableLookup {
+ /** Windows-formatted working directory; defaults to the process CWD. */
+ readonly cwd?: string;
+ /** Test seam for Windows paths when running on a non-Windows host. */
+ readonly isFile?: (path: string) => boolean;
+}
+
+const splitWindowsPath = (value: string): readonly string[] => {
+ const entries: string[] = [];
+ let start = 0;
+ let quoted = false;
+ for (let index = 0; index < value.length; index += 1) {
+ if (value[index] === '"') quoted = !quoted;
+ else if (value[index] === ";" && !quoted) {
+ entries.push(value.slice(start, index));
+ start = index + 1;
+ }
+ }
+ entries.push(value.slice(start));
+ return entries.flatMap((entry) => {
+ if (entry.startsWith('"') && entry.endsWith('"') && entry.length >= 2)
+ return [entry.slice(1, -1)];
+ return entry.includes('"') ? [] : [entry];
+ });
+};
+
+const isRegularFile = (path: string): boolean => {
+ try {
+ return existsSync(path) && statSync(path).isFile();
+ } catch {
+ return false;
+ }
+};
+
+/** Resolve Windows bare command names against the captured child environment. */
+export const resolveWindowsProviderExecutable = (
+ executable: string,
+ environment: Readonly,
+ lookup: WindowsExecutableLookup = {},
+): string => {
+ const cwd = lookup.cwd ?? process.cwd();
+ const absolute = (...paths: string[]): string => win32.resolve(cwd, ...paths);
+ if (
+ win32.isAbsolute(executable) ||
+ executable.includes("/") ||
+ executable.includes("\\") ||
+ win32.parse(executable).root !== ""
+ )
+ return absolute(executable);
+
+ const pathValue = environment.PATH ?? environment.Path ?? environment.path;
+ if (pathValue === undefined) return executable;
+ const pathEntries = splitWindowsPath(pathValue);
+ const explicitExtension = win32.extname(executable).toLowerCase();
+ if (explicitExtension !== "" && ![".com", ".exe"].includes(explicitExtension))
+ return executable;
+ const extensions =
+ explicitExtension !== ""
+ ? [""]
+ : (environment.PATHEXT ?? environment.PathExt ?? ".COM;.EXE")
+ .split(";")
+ .filter((extension) =>
+ [".com", ".exe"].includes(extension.toLowerCase()),
+ );
+
+ for (const entry of pathEntries) {
+ if (entry.length === 0) continue;
+ for (const extension of extensions) {
+ const candidate = absolute(entry, `${executable}${extension}`);
+ if ((lookup.isFile ?? isRegularFile)(candidate)) return candidate;
+ }
+ }
+ return executable;
+};
diff --git a/src/composition/reverseEngineering.ts b/src/composition/reverseEngineering.ts
new file mode 100644
index 000000000..493e86b9c
--- /dev/null
+++ b/src/composition/reverseEngineering.ts
@@ -0,0 +1,59 @@
+import type { ReverseEngineeringServiceRuntimeOptions } from "../application/reverse/ReverseEngineeringService.js";
+import type { ReverseEngineeringCommandPort } from "../application/reverse/ReverseEngineeringCommandPort.js";
+import { ReverseEngineeringService } from "../application/reverse/ReverseEngineeringService.js";
+import {
+ objdumpCommand,
+ OBJDUMP_PROVIDER_IDENTITY,
+} from "../objdump/ObjdumpCommand.js";
+import {
+ rizinCommand,
+ RIZIN_PROVIDER_IDENTITY,
+} from "../rizin/RizinCommand.js";
+import { resolveWindowsProviderExecutable } from "./resolveWindowsProviderExecutable.js";
+
+export const createReverseEngineeringCommandPort = (
+ environment: Readonly,
+ platform: NodeJS.Platform = process.platform,
+): ReverseEngineeringCommandPort => ({
+ objdump: (input) => ({
+ executable: providerExecutable(
+ environment.REA_OBJDUMP_COMMAND ?? "objdump",
+ environment,
+ platform,
+ ),
+ arguments: objdumpCommand(input),
+ identity: OBJDUMP_PROVIDER_IDENTITY,
+ environmentOverrides: { DEBUGINFOD_URLS: "" },
+ }),
+ rizin: (input) => ({
+ executable: providerExecutable(
+ environment.REA_RIZIN_COMMAND ?? "rizin",
+ environment,
+ platform,
+ ),
+ arguments: rizinCommand(input),
+ identity: RIZIN_PROVIDER_IDENTITY,
+ }),
+});
+
+const providerExecutable = (
+ executable: string,
+ environment: Readonly,
+ platform: NodeJS.Platform,
+): string =>
+ platform === "win32"
+ ? resolveWindowsProviderExecutable(executable, environment)
+ : executable;
+
+/** Compose BYO reverse-engineering commands without acquiring tools at import time. */
+export const createReverseEngineeringService = (
+ environment: Readonly = process.env,
+ options: ReverseEngineeringServiceRuntimeOptions = {},
+): ReverseEngineeringService => {
+ const environmentSnapshot = { ...environment };
+ return new ReverseEngineeringService({
+ ...options,
+ environment: environmentSnapshot,
+ commands: createReverseEngineeringCommandPort(environmentSnapshot),
+ });
+};
diff --git a/src/contracts/cutterToolContracts.ts b/src/contracts/cutterToolContracts.ts
new file mode 100644
index 000000000..e978251b8
--- /dev/null
+++ b/src/contracts/cutterToolContracts.ts
@@ -0,0 +1,96 @@
+import { z } from "zod";
+import { jsonValueSchema } from "../domain/jsonValue.js";
+import type { ToolContract } from "./toolContractTypes.js";
+import { evidenceResultOf } from "./toolOutputSchemaPrimitives.js";
+import { toolContractMetadata } from "./toolEffects.js";
+
+/** List live Cutter plugin bridge sessions. */
+export const cutterListInputSchema = z.strictObject({});
+
+/** Execute a command in a selected live Cutter instance and observed document generation. */
+export const cutterCommandInputSchema = z.strictObject({
+ session_id: z.string().uuid(),
+ expected_generation: z.number().int().nonnegative(),
+ command: z
+ .string()
+ .min(1)
+ .refine(
+ (value) => !/[\r\n]/u.test(value),
+ "A single Rizin command line is required",
+ ),
+ json: z.boolean().default(false),
+});
+
+export const cutterSessionSchema = z.strictObject({
+ session_id: z.string().uuid(),
+ pid: z.number().int().positive(),
+ document_generation: z.number().int().nonnegative(),
+ current_file: z.string().nullable(),
+ cutter_version: z.string().nullable(),
+ identity_status: z.literal("partial"),
+});
+
+export const cutterCommandResultSchema = z.strictObject({
+ command: z.string(),
+ output: jsonValueSchema.nullable(),
+ execution_state: z.enum(["not_started", "complete", "failed", "unknown"]),
+ error: z.string().nullable(),
+ message: z.string().nullable(),
+ output_truncated: z.boolean(),
+ cutter_version: z.string().nullable(),
+ current_file: z.string().nullable(),
+ document_generation: z.number().int().nonnegative(),
+ identity_status: z.literal("partial"),
+});
+
+export const cutterDiscoverySchema = z.strictObject({
+ sessions: z.array(cutterSessionSchema),
+ bridge_directory: z.string(),
+ discovery_status: z.enum([
+ "sessions_found",
+ "no_live_bridge_found",
+ "bridge_directory_unavailable",
+ "bridge_directory_insecure",
+ "bridge_directory_capacity_exceeded",
+ ]),
+ bridge_directory_security: z.enum([
+ "private_verified",
+ "not_private",
+ "unverified_platform_acl",
+ "not_checked",
+ ]),
+});
+
+/** Live Cutter discovery and arbitrary Rizin command contracts. */
+export const CUTTER_TOOL_CONTRACTS = [
+ {
+ name: "list_cutter_sessions",
+ ...toolContractMetadata("list_cutter_sessions"),
+ kind: "native-provider",
+ description:
+ "List live REA bridge sessions hosted by upstream Cutter instances, including observed document identity and bridge-directory security status. Discovery admits at most 256 descriptor candidates (16 MiB at the descriptor byte limit), reads and probes at most eight concurrently, and reports bridge_directory_capacity_exceeded rather than claiming no sessions when that budget is exceeded.",
+ inputSchema: cutterListInputSchema,
+ outputSchema: cutterDiscoverySchema,
+ examples: [{ title: "List open Cutter instances", input: {} }],
+ },
+ {
+ name: "cutter_command",
+ ...toolContractMetadata("cutter_command"),
+ kind: "native-provider",
+ description:
+ "Run one arbitrary Rizin command in a selected live Cutter session. Commands may edit and persist analysis state or execute plugin-provided effects; completion uncertainty is returned as Evidence and must not be retried automatically. In a shared MCP server, this admitted command closes any active binary-analysis session; after its effects have settled, call open_binary before further analysis. Close an active Ghidra session explicitly first.",
+ inputSchema: cutterCommandInputSchema,
+ outputSchema: evidenceResultOf(cutterCommandResultSchema),
+ examples: [
+ {
+ title: "Inspect active binary metadata",
+ input: {
+ session_id: "00000000-0000-4000-8000-000000000001",
+ expected_generation: 0,
+ command: "ij",
+ json: true,
+ },
+ },
+ ],
+ },
+] as const satisfies readonly ToolContract[];
diff --git a/src/contracts/gdbToolContracts.ts b/src/contracts/gdbToolContracts.ts
new file mode 100644
index 000000000..f1858a14a
--- /dev/null
+++ b/src/contracts/gdbToolContracts.ts
@@ -0,0 +1,113 @@
+import { z } from "zod";
+
+import type { ToolContract } from "./toolContractTypes.js";
+import { evidenceResultOf } from "./toolOutputSchemaPrimitives.js";
+import { toolContractMetadata } from "./toolEffects.js";
+
+const sessionId = z.string().uuid();
+const gdbCommandResult = z.strictObject({
+ command: z.string(),
+ mi: z.string(),
+ records: z.array(z.string()),
+ console: z.string(),
+ target: z.string(),
+ log: z.string(),
+ process_exit_observed: z.boolean().optional(),
+ process_exit_code: z.number().int().nullable().optional(),
+ process_exit_signal: z.string().nullable().optional(),
+ output_truncated: z.boolean(),
+ output_invalid_utf8: z.literal(true).optional(),
+ completion_status: z.enum(["complete", "unknown"]),
+});
+
+/** Persistent GDB/MI session contracts hosted by the existing REA MCP server. */
+export const GDB_TOOL_CONTRACTS = [
+ {
+ name: "start_gdb_session",
+ ...toolContractMetadata("start_gdb_session"),
+ kind: "native-provider",
+ description:
+ "Start a BYO GDB process in MI3 mode when available, suppress startup files, and disable auto-loading. Persistent GDB sessions are available only on Linux, where REA can verify live inferior output descriptors through /proc before detaching; macOS descriptor verification is not implemented, and Windows Job Object cleanup can terminate debugger inferiors. Optionally set inferior_tty to a caller-selected terminal for future inferior runs; REA verifies the setting through GDB/MI before the session becomes ready. The selected terminal must remain available after GDB exits. A live inferior can be detached during close only while this durable output sink remains verified. The session persists in this REA MCP server process and receives an explicit session ID.",
+ inputSchema: z.strictObject({
+ inferior_tty: z
+ .string()
+ .min(1)
+ .optional()
+ .describe(
+ "Caller-selected terminal path for future inferior runs. GDB must report the exact configured path before the session is ready.",
+ ),
+ }),
+ outputSchema: z.strictObject({
+ session_id: sessionId,
+ mi_version: z.enum(["mi3", "mi2"]),
+ }),
+ examples: [{ title: "Start GDB", input: {} }],
+ },
+ {
+ name: "gdb_console",
+ ...toolContractMetadata("gdb_console"),
+ kind: "native-provider",
+ description:
+ "Run one unrestricted GDB CLI command through MI interpreter-exec console and return raw MI, console, target, and log records inline with Evidence. A command timeout preserves collected records with completion_status unknown and stops the owned debugger session. Commands can execute shell, alter target state, access files, and use network. Closing the debugger never implies target termination. In a shared MCP server, this admitted command closes any active binary-analysis session; after its effects have settled, call open_binary before further analysis. Close an active Ghidra session explicitly first.",
+ inputSchema: z.strictObject({
+ session_id: sessionId,
+ command: z.string().min(1),
+ }),
+ outputSchema: evidenceResultOf(gdbCommandResult),
+ examples: [
+ {
+ title: "Inspect loaded symbols",
+ input: {
+ session_id: "00000000-0000-4000-8000-000000000001",
+ command: "info functions",
+ },
+ },
+ ],
+ },
+ {
+ name: "gdb_session_status",
+ ...toolContractMetadata("gdb_session_status"),
+ kind: "native-provider",
+ description:
+ "Return the observed lifecycle state and bounded recent MI records and process diagnostics for one persistent REA GDB session; truncation and invalid-UTF-8 fields report discarded or lossy data.",
+ inputSchema: z.strictObject({ session_id: sessionId }),
+ outputSchema: z.strictObject({
+ session_id: sessionId,
+ mi_version: z.enum(["mi3", "mi2"]),
+ state: z.enum(["ready", "closed"]),
+ recent_mi_records: z.array(z.string()),
+ recent_mi_records_truncated: z.boolean(),
+ mi_invalid_utf8: z.boolean(),
+ stderr: z.string(),
+ stderr_observed_bytes: z.number().int().nonnegative(),
+ stderr_truncated: z.boolean(),
+ stderr_invalid_utf8: z.boolean(),
+ diagnostics_truncated: z.boolean(),
+ inferior_tty: z.string().nullable(),
+ inferior_tty_verified: z.boolean(),
+ }),
+ examples: [
+ {
+ title: "Check GDB session",
+ input: { session_id: "00000000-0000-4000-8000-000000000001" },
+ },
+ ],
+ },
+ {
+ name: "close_gdb_session",
+ ...toolContractMetadata("close_gdb_session"),
+ kind: "native-provider",
+ description:
+ "Close the owned GDB process after verifying the inferior state. When one live thread group has a verified durable output TTY, REA automatically detaches it, which may resume target execution; otherwise close reports that a disposition is required. REA never kills attached or remote targets as part of debugger cleanup. In a shared MCP server, this admitted close retires any active binary-analysis session; after target effects have settled, call open_binary before further analysis. Close an active Ghidra session explicitly first.",
+ inputSchema: z.strictObject({ session_id: sessionId }),
+ outputSchema: z.strictObject({
+ target_state: z.enum(["none_observed", "unknown"]),
+ }),
+ examples: [
+ {
+ title: "Close GDB session",
+ input: { session_id: "00000000-0000-4000-8000-000000000001" },
+ },
+ ],
+ },
+] as const satisfies readonly ToolContract[];
diff --git a/src/contracts/reverseEngineeringToolContracts.ts b/src/contracts/reverseEngineeringToolContracts.ts
new file mode 100644
index 000000000..7b6542be1
--- /dev/null
+++ b/src/contracts/reverseEngineeringToolContracts.ts
@@ -0,0 +1,61 @@
+import { z } from "zod";
+
+import {
+ objdumpInputSchema,
+ rizinInputSchema,
+} from "../domain/reverseEngineering.js";
+import type { ToolContract } from "./toolContractTypes.js";
+import { evidenceResultOf } from "./toolOutputSchemaPrimitives.js";
+import { toolContractMetadata } from "./toolEffects.js";
+
+const commandObservationSchema = z.strictObject({
+ stdout: z.string(),
+ stderr: z.string(),
+ exit_code: z.union([z.string(), z.number(), z.null()]),
+ signal: z.string().nullable(),
+ output_truncated: z.boolean(),
+ completion_status: z.enum(["complete", "unknown"]),
+ artifact_sha256_after: z
+ .string()
+ .regex(/^[a-f0-9]{64}$/u)
+ .nullable(),
+ artifact_changed: z.boolean().nullable(),
+});
+
+/** Caller-selected GNU objdump and Rizin observations. */
+export const REVERSE_ENGINEERING_TOOL_CONTRACTS = [
+ {
+ name: "inspect_with_objdump",
+ ...toolContractMetadata("inspect_with_objdump"),
+ kind: "native-provider",
+ description:
+ "Run caller-selected GNU objdump inspection against a local artifact, or request operation=architectures without a path to list BFD targets advertised by the installed binary. Preserves decoded stdout and stderr inline with SHA-256 Evidence for artifact inspections, up to a shared 16 MiB combined limit; invalid UTF-8 bytes are represented by replacement characters and flagged in Evidence limitations. output_truncated reports output beyond that limit and completion_status is unknown if buffer enforcement terminated the process. The raw output is authoritative and REA does not claim a stable normalized format. DWARF does not follow local debug links by default. Set follow_debug_links to true to follow them; identities of external debug files are not bound to Evidence. REA clears DEBUGINFOD_URLS for objdump so DWARF processing does not make debuginfod network requests. Architecture output is subjectless and does not establish support for any artifact.",
+ inputSchema: objdumpInputSchema,
+ outputSchema: evidenceResultOf(commandObservationSchema),
+ examples: [
+ {
+ title: "Disassemble code sections",
+ input: { path: "/tmp/program", operation: "disassemble" },
+ },
+ {
+ title: "List advertised BFD targets",
+ input: { operation: "architectures" },
+ },
+ ],
+ },
+ {
+ name: "execute_rizin_command",
+ ...toolContractMetadata("execute_rizin_command"),
+ kind: "native-provider",
+ description:
+ "Execute one caller-selected Rizin command against a local artifact using the Rizin CLI profile that suppresses user settings and scripts while retaining plugins. Preserves decoded stdout and stderr inline with SHA-256 Evidence, up to a shared 16 MiB combined limit; invalid UTF-8 bytes are represented by replacement characters and flagged in Evidence limitations. output_truncated reports output beyond that limit and completion_status is unknown if buffer enforcement terminated the process. Rizin commands and retained plugins can execute foreign code, mutate files, access the network, and read files outside the selected artifact; identities and contents of those external inputs are not bound to Evidence. Command effects depend on the Rizin command and loaded plugins; JSON is command-specific and raw output remains authoritative. In a shared MCP server, this admitted command closes any active binary-analysis session; after its effects have settled, call open_binary before further analysis. Close an active Ghidra session explicitly first.",
+ inputSchema: rizinInputSchema,
+ outputSchema: evidenceResultOf(commandObservationSchema),
+ examples: [
+ {
+ title: "Inspect binary information",
+ input: { path: "/tmp/program", command: "iI" },
+ },
+ ],
+ },
+] as const satisfies readonly ToolContract[];
diff --git a/src/contracts/rizinDebugToolContracts.ts b/src/contracts/rizinDebugToolContracts.ts
new file mode 100644
index 000000000..d638a143e
--- /dev/null
+++ b/src/contracts/rizinDebugToolContracts.ts
@@ -0,0 +1,107 @@
+import { z } from "zod";
+
+import type { ToolContract } from "./toolContractTypes.js";
+import { evidenceResultOf } from "./toolOutputSchemaPrimitives.js";
+import { toolContractMetadata } from "./toolEffects.js";
+
+const sessionId = z.string().uuid();
+const commandResult = z.strictObject({
+ command: z.string(),
+ output: z.string(),
+ output_scope: z.literal("command_and_interleaved_session_output"),
+ output_truncated: z.boolean(),
+ output_invalid_utf8: z.literal(true).optional(),
+ completion_status: z.enum(["complete", "unknown"]),
+ backend: z.string().nullable(),
+});
+
+/** Persistent Rizin debugger session contracts. */
+export const RIZIN_DEBUG_TOOL_CONTRACTS = [
+ {
+ name: "start_rizin_debug_session",
+ ...toolContractMetadata("start_rizin_debug_session"),
+ kind: "native-provider",
+ description:
+ "Start a persistent Rizin debugger session using `-N` to suppress user settings and scripts while retaining plugins, `-0` for NUL framing, and `-d` with an optional caller-selected IO debugger backend. Requires a caller-selected Linux TTY for inferior stdout and stderr; the target may write directly to that terminal while running and after detach. REA verifies both destinations and `dbg.exitkills=false` before detaching. Backend and target capabilities depend on the installed build. The session lives in this REA MCP process. Persistent sessions require Linux `/proc` descriptor readback. In a shared MCP server, starting this session retires any active binary-analysis session; after target effects have settled, call open_binary before further analysis. Close an active Ghidra session explicitly first.",
+ inputSchema: z.strictObject({
+ path: z.string().min(1),
+ inferior_tty: z.string().min(1),
+ backend: z.string().min(1).optional(),
+ }),
+ outputSchema: z.strictObject({
+ session_id: sessionId,
+ backend: z.string().nullable(),
+ }),
+ examples: [
+ {
+ title: "Start Rizin debugger",
+ input: { path: "/tmp/program", inferior_tty: "/dev/pts/7" },
+ },
+ ],
+ },
+ {
+ name: "rizin_debug_command",
+ ...toolContractMetadata("rizin_debug_command"),
+ kind: "native-provider",
+ description:
+ "Execute one unrestricted command in a persistent Rizin debugger session. Returns raw output inline with Evidence, including backend output interleaved before the completion marker. If the command exceeds the output or frame limit, `output_truncated` is true and `completion_status` is `unknown`; retained partial output is returned and the owned debugger session is stopped. Unsolicited frames already queued before the command make the session unavailable because correlation is ambiguous. Commands can access local files, shell, network, plugins, and target state; effects depend on the active backend and target. In a shared MCP server, this admitted command closes any active binary-analysis session; after its effects have settled, call open_binary before further analysis. Close an active Ghidra session explicitly first.",
+ inputSchema: z.strictObject({
+ session_id: sessionId,
+ command: z
+ .string()
+ .min(1)
+ .refine(
+ (value) => !/[\r\n]/u.test(value),
+ "A single command line is required",
+ ),
+ }),
+ outputSchema: evidenceResultOf(commandResult),
+ examples: [
+ {
+ title: "Inspect debugger state",
+ input: {
+ session_id: "00000000-0000-4000-8000-000000000001",
+ command: "dr",
+ },
+ },
+ ],
+ },
+ {
+ name: "rizin_debug_session_status",
+ ...toolContractMetadata("rizin_debug_session_status"),
+ kind: "native-provider",
+ description:
+ "Return observed lifecycle state and bounded recent NUL-framed output and process diagnostics for one Rizin debugger session; truncation fields report discarded data, and output_invalid_utf8 reports whether invalid UTF-8 has been observed in session frame bytes; recent output may contain replacement characters.",
+ inputSchema: z.strictObject({ session_id: sessionId }),
+ outputSchema: z.strictObject({
+ session_id: sessionId,
+ state: z.enum(["ready", "closed"]),
+ inferior_tty: z.string(),
+ recent_output: z.array(z.string()),
+ recent_output_truncated: z.boolean(),
+ diagnostics_truncated: z.boolean(),
+ output_invalid_utf8: z.boolean(),
+ }),
+ examples: [
+ {
+ title: "Check Rizin session",
+ input: { session_id: "00000000-0000-4000-8000-000000000001" },
+ },
+ ],
+ },
+ {
+ name: "close_rizin_debug_session",
+ ...toolContractMetadata("close_rizin_debug_session"),
+ kind: "native-provider",
+ description:
+ "Close the REA-owned Rizin debugger only after verifying no inferior remains or detaching one with a verified caller-selected TTY output sink. A detached target may continue running and write to that TTY; target state is otherwise reported as unknown. REA never signals an inferior based on PID or path. In a shared MCP server, this admitted close retires any active binary-analysis session; after target effects have settled, call open_binary before further analysis. Close an active Ghidra session explicitly first.",
+ inputSchema: z.strictObject({ session_id: sessionId }),
+ outputSchema: z.strictObject({ target_state: z.literal("unknown") }),
+ examples: [
+ {
+ title: "Close Rizin debugger",
+ input: { session_id: "00000000-0000-4000-8000-000000000001" },
+ },
+ ],
+ },
+] as const satisfies readonly ToolContract[];
diff --git a/src/contracts/toolContractExamples.ts b/src/contracts/toolContractExamples.ts
index 50a745e75..dc6567e39 100644
--- a/src/contracts/toolContractExamples.ts
+++ b/src/contracts/toolContractExamples.ts
@@ -82,6 +82,11 @@ export const TOOL_EXAMPLE_OVERRIDES: Readonly<
left_bundle_path: "/tmp/left-evidence.json",
right_bundle_path: "/tmp/right-evidence.json",
},
+ inspect_with_objdump: {
+ path: "/tmp/program",
+ operation: "disassemble",
+ },
+ execute_rizin_command: { path: "/tmp/program", command: "iI" },
list_frida_devices: {},
list_frida_processes: { device_id: "local" },
start_frida_session: { mode: "attach", device_id: "local", pid: 1234 },
diff --git a/src/contracts/toolContracts.ts b/src/contracts/toolContracts.ts
index 39b65b3d2..91627ee5b 100644
--- a/src/contracts/toolContracts.ts
+++ b/src/contracts/toolContracts.ts
@@ -29,6 +29,10 @@ import { JAVASCRIPT_RECOVERY_TOOL_CONTRACTS } from "./javascript/javascriptRecov
import { OFFICIAL_TOOL_CONTRACTS } from "./officialToolContracts.js";
import { ENHANCED_TOOL_CONTRACTS } from "./enhancedToolContracts.js";
import { SESSION_TOOL_CONTRACTS } from "./sessionToolContracts.js";
+import { CUTTER_TOOL_CONTRACTS } from "./cutterToolContracts.js";
+import { REVERSE_ENGINEERING_TOOL_CONTRACTS } from "./reverseEngineeringToolContracts.js";
+import { GDB_TOOL_CONTRACTS } from "./gdbToolContracts.js";
+import { RIZIN_DEBUG_TOOL_CONTRACTS } from "./rizinDebugToolContracts.js";
import { FRIDA_TOOL_CONTRACTS } from "./fridaToolContracts.js";
/** Complete ordered public inventory used by registration and verification. */
@@ -64,6 +68,10 @@ export const TOOL_CONTRACTS = [
...WEB_NETWORK_CAPTURE_TOOL_CONTRACTS,
...JAVASCRIPT_RECOVERY_TOOL_CONTRACTS,
...SESSION_TOOL_CONTRACTS,
+ ...CUTTER_TOOL_CONTRACTS,
+ ...REVERSE_ENGINEERING_TOOL_CONTRACTS,
+ ...GDB_TOOL_CONTRACTS,
+ ...RIZIN_DEBUG_TOOL_CONTRACTS,
...FRIDA_TOOL_CONTRACTS,
] as const;
diff --git a/src/contracts/toolEffects.test.ts b/src/contracts/toolEffects.test.ts
index adf4d0932..7d23db92d 100644
--- a/src/contracts/toolEffects.test.ts
+++ b/src/contracts/toolEffects.test.ts
@@ -13,6 +13,10 @@ const EXECUTION_WORDING =
/\b(?:run|runs|running|launch|launches|launching|execute|executes|executing|execution|start|starts|install|installs|uninstall|uninstalls)\b/iu;
const EXECUTION_WORDING_EXCEPTIONS: Readonly> = {
+ inspect_with_objdump:
+ "runs a caller-selected inspection tool, not target code",
+ start_gdb_session:
+ "starts a debugger session; execution is attributed to gdb_console",
start_frida_session:
"spawned targets remain suspended until resume_frida_session",
inspect_native_load_image: "states that it does not execute the target",
@@ -129,14 +133,21 @@ describe("tool effects", () => {
"capture_electron_scenario",
"capture_native_ui_scenario",
"capture_process_scenario",
+ "close_gdb_session",
+ "close_rizin_debug_session",
+ "cutter_command",
+ "execute_rizin_command",
+ "gdb_console",
"install_adb_package",
"instrument_with_frida",
"load_frida_script",
"observe_native_calls",
"push_adb_file",
"resume_frida_session",
+ "rizin_debug_command",
"start_adb_activity",
"start_adb_app",
+ "start_rizin_debug_session",
"stop_adb_app",
"uninstall_adb_package",
]);
@@ -169,6 +180,17 @@ describe("tool effects", () => {
});
});
+ it("marks automatic GDB detach as destructive target execution", () => {
+ const effects = TOOL_EFFECTS["close_gdb_session"];
+ if (effects === undefined)
+ throw new Error("close_gdb_session is unaudited");
+ expect(effects).toMatchObject({
+ mutatesTarget: true,
+ executesForeignCode: true,
+ });
+ expect(annotationsFromEffects(effects).destructiveHint).toBe(true);
+ });
+
it("publishes the advertised annotations derived from canonical effects", () => {
for (const contract of TOOL_CONTRACTS)
expect(contract.annotations, contract.name).toEqual(
diff --git a/src/contracts/toolEffects.ts b/src/contracts/toolEffects.ts
index 137e54801..c64edefca 100644
--- a/src/contracts/toolEffects.ts
+++ b/src/contracts/toolEffects.ts
@@ -53,6 +53,78 @@ const sessionEvidence = effects({ mutatesSession: true });
/** Explicit effect audit for every public tool. */
export const TOOL_EFFECTS: Readonly> = {
+ list_cutter_sessions: effects({ accessesNetwork: true }),
+ cutter_command: effects({
+ mutatesTarget: true,
+ executesForeignCode: true,
+ mutatesSession: true,
+ writesFilesystem: true,
+ launchesProcess: true,
+ accessesNetwork: true,
+ changesUiState: true,
+ idempotent: false,
+ }),
+ inspect_with_objdump: effects({
+ mutatesSession: true,
+ launchesProcess: true,
+ }),
+ start_gdb_session: effects({
+ launchesProcess: true,
+ mutatesSession: true,
+ idempotent: false,
+ }),
+ gdb_console: effects({
+ mutatesTarget: true,
+ mutatesSession: true,
+ writesFilesystem: true,
+ launchesProcess: true,
+ accessesNetwork: true,
+ executesForeignCode: true,
+ idempotent: false,
+ }),
+ gdb_session_status: effects(),
+ start_rizin_debug_session: effects({
+ mutatesTarget: true,
+ mutatesSession: true,
+ launchesProcess: true,
+ executesForeignCode: true,
+ idempotent: false,
+ }),
+ rizin_debug_command: effects({
+ mutatesTarget: true,
+ mutatesSession: true,
+ executesForeignCode: true,
+ writesFilesystem: true,
+ launchesProcess: true,
+ accessesNetwork: true,
+ changesUiState: true,
+ mayDiscardData: true,
+ idempotent: false,
+ }),
+ rizin_debug_session_status: effects({ mutatesSession: true }),
+ close_rizin_debug_session: effects({
+ mutatesSession: true,
+ launchesProcess: true,
+ mutatesTarget: true,
+ executesForeignCode: true,
+ }),
+ close_gdb_session: effects({
+ launchesProcess: true,
+ mutatesSession: true,
+ mutatesTarget: true,
+ executesForeignCode: true,
+ idempotent: false,
+ }),
+ execute_rizin_command: effects({
+ mutatesTarget: true,
+ mutatesSession: true,
+ writesFilesystem: true,
+ launchesProcess: true,
+ accessesNetwork: true,
+ mayDiscardData: true,
+ executesForeignCode: true,
+ idempotent: false,
+ }),
inspect_go_binary: evidence,
inspect_rust_binary: evidence,
inspect_sqlite_database: effects({
diff --git a/src/cutter/CutterBridgeClient.ts b/src/cutter/CutterBridgeClient.ts
new file mode 100644
index 000000000..13973cabf
--- /dev/null
+++ b/src/cutter/CutterBridgeClient.ts
@@ -0,0 +1,891 @@
+import { randomUUID } from "node:crypto";
+import { constants as fsConstants } from "node:fs";
+import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
+import { createConnection } from "node:net";
+import { lstat, open, opendir } from "node:fs/promises";
+import { homedir } from "node:os";
+import { join } from "node:path";
+import { z } from "zod";
+
+import {
+ AnalysisCapabilityUnavailableError,
+ AnalysisOutputError,
+ AnalysisResourceConstraintError,
+} from "../domain/analysisErrorCore.js";
+import type { AnalysisError } from "../domain/analysisErrorBase.js";
+import { err, ok, type Result } from "../domain/result.js";
+import { requireWindowsNativeAuthority } from "../windows/WindowsNativeLoader.js";
+import { readBoundedFileBytes } from "../process/BoundedFileBytes.js";
+
+const descriptorSchema = z.object({
+ session_id: z.string().uuid(),
+ pid: z.number().int().positive(),
+ host: z.literal("127.0.0.1"),
+ port: z.number().int().min(1).max(65535),
+ token: z.string().min(20),
+ document_generation: z.number().int().nonnegative(),
+ current_file: z.string().nullable(),
+ cutter_version: z.string().nullable(),
+ identity_status: z.literal("partial"),
+});
+
+export const CUTTER_PROVIDER_IDENTITY = {
+ id: "cutter.python-plugin",
+ name: "Cutter Python plugin",
+ version: null,
+} as const;
+
+const responseSchema = z.object({
+ ok: z.boolean(),
+ session_id: z.string().uuid().optional(),
+ output: z.unknown().optional(),
+ current_file: z.string().nullable().optional(),
+ cutter_version: z.string().nullable().optional(),
+ document_generation: z.number().int().nonnegative().optional(),
+ identity_status: z.literal("partial").optional(),
+ error: z.string().optional(),
+ message: z.string().optional(),
+ execution_state: z
+ .enum(["not_started", "running", "complete", "failed", "unknown"])
+ .optional(),
+ output_truncated: z.boolean().optional(),
+ completed_commands: z
+ .array(
+ z.object({
+ request_id: z.string().uuid(),
+ execution_state: z.enum(["not_started", "complete", "failed"]),
+ }),
+ )
+ .max(64)
+ .optional(),
+ completed_connection: z
+ .string()
+ .regex(/^[0-9a-f]{16}$/)
+ .optional(),
+});
+const acknowledgementSchema = z.object({
+ ok: z.literal(true),
+ acknowledged: z.boolean(),
+});
+const MAX_DESCRIPTOR_BYTES = 64 * 1024;
+const MAX_FRAME_BYTES = 16 * 1024 * 1024;
+const FRAME_PAGE_BYTES = 64 * 1024;
+// Bound admitted descriptor input bytes and concurrent file/socket handles.
+const MAX_DESCRIPTOR_CANDIDATES = MAX_FRAME_BYTES / MAX_DESCRIPTOR_BYTES;
+const DISCOVERY_CONCURRENCY = 8;
+type AuthenticatedRequestBytes = string & {
+ readonly __authenticatedRequestBytes: unique symbol;
+};
+
+const serializeRequest = (
+ descriptor: z.infer,
+ request: Readonly>,
+): AuthenticatedRequestBytes => {
+ const requestBytes = JSON.stringify({
+ ...request,
+ session_id: descriptor.session_id,
+ });
+ let frameBytes =
+ Buffer.byteLength(JSON.stringify({ request: "", proof: "0".repeat(64) })) +
+ Buffer.byteLength(requestBytes);
+ for (const character of requestBytes)
+ if (character === '"' || character === "\\") frameBytes += 1;
+ if (frameBytes > MAX_FRAME_BYTES)
+ throw new AnalysisResourceConstraintError(
+ "cutter_command",
+ "transport",
+ "Cutter request exceeds its authenticated frame limit; no command was sent.",
+ { frame_bytes: frameBytes, max_frame_bytes: MAX_FRAME_BYTES },
+ );
+ return requestBytes as AuthenticatedRequestBytes;
+};
+
+class CutterBridgeResponseFrames {
+ #parts: Buffer[] = [];
+ #page = Buffer.allocUnsafe(FRAME_PAGE_BYTES);
+ #pageLength = 0;
+ #contentBytes = 0;
+ #framesRead = 0;
+
+ push(chunk: Buffer, onFrame: (line: Buffer) => boolean): void {
+ let offset = 0;
+ while (offset < chunk.length) {
+ const delimiter = chunk.indexOf(0x0a, offset);
+ const end = delimiter < 0 ? chunk.length : delimiter;
+ const segment = chunk.subarray(offset, end);
+ if (
+ this.#contentBytes + segment.length + (delimiter < 0 ? 1 : 0) >
+ MAX_FRAME_BYTES
+ )
+ throw new Error(
+ "Cutter bridge response exceeded its framed transport limit",
+ );
+ this.#append(segment);
+ if (delimiter < 0) return;
+ if (++this.#contentBytes > MAX_FRAME_BYTES)
+ throw new Error(
+ "Cutter bridge response exceeded its framed transport limit",
+ );
+ if (this.#pageLength > 0)
+ this.#parts.push(this.#page.subarray(0, this.#pageLength));
+ const line = Buffer.concat(this.#parts, this.#contentBytes - 1);
+ this.#reset();
+ if (++this.#framesRead > 2)
+ throw new Error("Cutter bridge sent more than two protocol frames");
+ offset = delimiter + 1;
+ if (!onFrame(line)) return;
+ }
+ }
+
+ #append(segment: Buffer): void {
+ let copied = 0;
+ while (copied < segment.length) {
+ const length = Math.min(
+ segment.length - copied,
+ FRAME_PAGE_BYTES - this.#pageLength,
+ );
+ segment.copy(this.#page, this.#pageLength, copied, copied + length);
+ this.#pageLength += length;
+ this.#contentBytes += length;
+ copied += length;
+ if (this.#pageLength === FRAME_PAGE_BYTES) {
+ this.#parts.push(this.#page);
+ this.#page = Buffer.allocUnsafe(FRAME_PAGE_BYTES);
+ this.#pageLength = 0;
+ }
+ }
+ }
+
+ #reset(): void {
+ this.#parts = [];
+ this.#page = Buffer.allocUnsafe(FRAME_PAGE_BYTES);
+ this.#pageLength = 0;
+ this.#contentBytes = 0;
+ }
+}
+
+const mapDiscovery = async (
+ inputs: readonly Input[],
+ operation: (input: Input) => Promise