docs(website): add worked guides and keep Pages publishing manual (#925)

* fix(pages): reserve publishing for the REA website

* docs(website): add worked guides and agent prompt examples

* ci(website): check relevant PRs and publish only on demand

* docs(website): clarify IDA coverage and analysis session lifetime
This commit is contained in:
ysopwnable 2026-10-07 18:40:11 +08:00 committed by GitHub
parent b9292516f4
commit 4f25ee33dc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 1817 additions and 102 deletions

View File

@ -1,30 +1,14 @@
name: Documentation site
name: Documentation build
on:
push:
branches: [main]
paths:
- "docs/**"
- "package.json"
- "package-lock.json"
- ".nvmrc"
- ".github/workflows/pages.yml"
pull_request:
branches: [main]
paths:
- "docs/**"
- "package.json"
- "package-lock.json"
- ".nvmrc"
- ".github/workflows/pages.yml"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: pages-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
group: documentation-build-${{ github.ref }}
cancel-in-progress: true
env:
HUSKY: "0"
@ -33,6 +17,7 @@ jobs:
build:
name: Build documentation
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
@ -41,23 +26,3 @@ jobs:
cache: npm
- run: npm ci
- run: npm run docs:build
- uses: actions/upload-pages-artifact@v4
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
with:
path: docs/.vitepress/dist
deploy:
name: Publish documentation
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
needs: build
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- uses: actions/configure-pages@v5
- uses: actions/deploy-pages@v4
id: deployment

28
.github/workflows/website-check.yml vendored Normal file
View File

@ -0,0 +1,28 @@
name: Website checks
on:
pull_request:
branches: [main]
paths:
- "website/**"
- "scripts/verify-website.py"
- ".github/workflows/**"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: website-check-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
check:
name: Website checks
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Check out the repository
uses: actions/checkout@v4
- name: Check website links and publishing configuration
run: python3 scripts/verify-website.py

View File

@ -21,6 +21,8 @@ jobs:
steps:
- name: Check out the repository
uses: actions/checkout@v4
- name: Check website links and publishing configuration
run: python3 scripts/verify-website.py
- name: Configure Pages
uses: actions/configure-pages@v5
- name: Upload the website

View File

@ -28,7 +28,8 @@
{
"files": [
"scripts/electron-active-hook*.cjs",
"tests/conformance/readiness/electron/*.js"
"tests/conformance/readiness/electron/*.js",
"website/public/examples/notes-electron/*.js"
],
"rules": {
"no-var-requires": "off"

106
scripts/verify-website.py Normal file
View File

@ -0,0 +1,106 @@
"""Check local website references and the sole GitHub Pages publisher."""
from html.parser import HTMLParser
from pathlib import Path
import re
import sys
from urllib.parse import unquote, urlsplit
import xml.etree.ElementTree as ET
class HtmlReferences(HTMLParser):
"""Collect document IDs and references without fetching external URLs."""
def __init__(self):
super().__init__()
self.ids = set()
self.references = []
self.duplicate_ids = []
def handle_starttag(self, tag, attrs):
attributes = dict(attrs)
identifier = attributes.get("id")
if identifier is not None:
if identifier in self.ids:
self.duplicate_ids.append(identifier)
self.ids.add(identifier)
for attribute in ("href", "src"):
reference = attributes.get(attribute)
if reference is not None:
self.references.append(reference)
def check_site(site):
"""Check assets, HTML fragment destinations and SVG syntax."""
pages = {}
errors = []
references = 0
for path in sorted(site.rglob("*.html")):
parser = HtmlReferences()
parser.feed(path.read_text(encoding="utf-8"))
pages[path] = parser
if site / "index.html" not in pages:
errors.append("website/public/index.html is missing")
for path, page in pages.items():
label = path.relative_to(site)
for identifier in page.duplicate_ids:
errors.append(f"{label}: duplicate ID {identifier!r}")
for reference in page.references:
url = urlsplit(reference)
if url.scheme or url.netloc:
continue
references += 1
if url.path.startswith("/"):
errors.append(f"{label}: use a relative site reference: {reference}")
continue
target = (path.parent / unquote(url.path)).resolve() if url.path else path
if not target.is_relative_to(site):
errors.append(f"{label}: reference leaves the website: {reference}")
continue
if target.is_dir():
target /= "index.html"
if not target.is_file():
errors.append(f"{label}: missing local target: {reference}")
elif target in pages and url.fragment and unquote(url.fragment) not in pages[target].ids:
errors.append(f"{label}: missing HTML fragment: {reference}")
for path in sorted(site.rglob("*.svg")):
try:
ET.parse(path)
except ET.ParseError as error:
errors.append(f"{path.relative_to(site)}: invalid SVG XML: {error}")
return len(pages), references, errors
def check_publisher(root):
"""Detect a second Pages action that could replace the website."""
expected = root / ".github/workflows/website-pages.yml"
workflows = root / ".github/workflows"
deploy_action = re.compile(r"^\s*-?\s*uses:\s*['\"]?actions/deploy-pages@", re.MULTILINE)
publishers = [
path
for path in sorted(workflows.iterdir())
if path.suffix in (".yml", ".yaml") and deploy_action.search(path.read_text(encoding="utf-8"))
]
if publishers != [expected]:
actual = ", ".join(str(path.relative_to(root)) for path in publishers) or "none"
return [f"Pages must have one publisher, website-pages.yml; found: {actual}"]
return []
def main():
"""Run the same checks locally, on website PRs and before publishing."""
root = Path(__file__).resolve().parent.parent
pages, references, errors = check_site(root / "website/public")
errors.extend(check_publisher(root))
if errors:
print("Website checks failed:\n" + "\n".join(errors), file=sys.stderr)
return 1
print(f"Verified {pages} HTML pages, {references} local references and SVG XML.")
print("website-pages.yml is the sole GitHub Pages publisher.")
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@ -1,6 +1,6 @@
# REA website
An English static website with explanatory figures and a DX-Ball investigation.
An English static website with explanatory figures, worked guides and a DX-Ball investigation.
The public files are in `website/public/`. The site uses HTML, CSS and a small
script for copying code and following the assembly-to-C comparison; it has no
build step or npm dependencies.
@ -20,6 +20,8 @@ Open <http://127.0.0.1:4173/>. Refresh the browser after editing a file.
- `public/index.html`: product introduction and DX-Ball overview.
- `public/showcase/dx-ball/index.html`: sound-pan investigation and project status.
- `public/get-started/index.html`: agent setup, first CLI result and provider guides.
- `public/guides/`: a guide hub and native, JavaScript/Electron and browser examples.
- `public/examples/`: downloadable Electron source and an interactive Notes browser app.
- `public/assets/`: shared styles, interactions, favicon and explanatory figures.
Navigation and assets use relative paths, so the same files work at the local
@ -29,7 +31,18 @@ root and a GitHub Pages project path such as `/rea/`.
Keep the copy direct and specific. Explain the task and the result before listing
tool names. Setup commands and runtime requirements should match the released
package.
package. Keep the core learning path inside the site. Source, issue and evidence
reference links may point to the corresponding repositories.
[evidence/guide-examples.md](evidence/guide-examples.md) records the published
REA package, example digests and observed results behind the guides. The Notes
Electron fixture is for static analysis; Electron is not a prerequisite for
following that example. The separate Notes browser app runs in the local preview
and constructs a CSV download after fetching its JSON data.
Agent terminals show example prompts, not transcripts of previous
investigations. A short cursor animation starts when the prompt enters view and
respects reduced-motion preferences. All prompt text is present without JavaScript.
DX-Ball figures and findings refer to the linked 7 October 2026 checkpoint,
commit `a55dca27ec0a07018c1b2c95ae2be027f7d8c3c4`. Update those links and figures
@ -41,23 +54,47 @@ Evidence. [evidence/dx-ball-sound-pan.md](evidence/dx-ball-sound-pan.md) records
their provenance and the scope of the validation claims. The original executable
and complete private Evidence records are not website assets.
The diagrams are maintained as SVG source. Initial layout references were
The homepage and DX-Ball overview diagrams are maintained as SVG source. Initial layout references were
created with the built-in image generation tool; [figures.md](figures.md) retains
their prompts and the current asset notes. Figures provide an overview;
REA requests, assembly and C remain selectable HTML text. On narrow
screens, the diagrams scroll horizontally and can also be opened at full size.
The worked guides use semantic HTML flows that stack vertically on smaller
screens. The Electron teaching example uses CommonJS, matching its preload
code; the scoped lint override admits `require` only in that example directory.
## GitHub Pages
`.github/workflows/website-pages.yml` prepares and deploys only `website/public`.
It is manually triggered and restricted to `main`; ordinary pushes and pull
requests do not publish the site.
It is the sole Pages publisher, manually triggered and restricted to `main`;
ordinary pushes and pull requests do not publish the site.
Website checks run only for pull requests that change `website/`, the verification
script or workflow definitions. They check local links, HTML fragments, SVG XML
and the single Pages publisher without installing npm dependencies. The same
checks run before each manual deployment. You can also run them locally:
```sh
python3 scripts/verify-website.py
```
`.github/workflows/pages.yml` is a separate, manual-only VitePress build. It
has no Pages artifact upload, deployment job or deployment permissions. This
prevents documentation updates from replacing the public website. The legacy
publishing job has been removed, so enabling this build workflow cannot publish
the old site.
After the site is approved and merged, select **GitHub Actions** under the
repository's **Settings → Pages → Build and deployment**. Then run **Publish REA
website** from the Actions tab on `main`. The workflow uses the `github-pages`
environment and the official Pages actions.
The equivalent CLI command is:
```sh
gh workflow run website-pages.yml --repo morluto/rea --ref main
```
Local development does not change Pages settings or run the deployment workflow.
Any environment protection rules are configured separately when publication is
approved.

View File

@ -0,0 +1,78 @@
# Guide example evidence
The native guide reuses the recorded DX-Ball observations in
[dx-ball-sound-pan.md](dx-ball-sound-pan.md). No new native-provider claim is
introduced by this iteration.
## Notes Electron example
This six-file, source-owned teaching fixture is in
`website/public/examples/notes-electron/`. It is a static analysis target, not
an installed Electron distribution. Its graph was obtained using the released
`rea-agents` 4.1.0 package, Node.js 24.18.0 on Linux x64, on 7 October 2026:
```sh
rea analyze-javascript-application \
/absolute/path/to/website/public/examples/notes-electron --json
```
- Evidence ID: `ev_a7e68cbdbd52ee15a959047774d1164d385e3e5ceb704084395639d97fee093d`.
- Root artifact SHA-256: `dae5292f341b4f0dfd5be0846d4cfa0d4821f3e062e970cc1cd568c18c3fb37c`.
- Four JavaScript files parsed, zero parse failures.
- One exposed API member, one literal IPC channel, one renderer invocation,
one main-process handler, one paired invocation, zero ambiguous invocations.
- The guide's result excerpt comes from `normalized_result.summary.ipc`.
- The `exposes`, `invokes` and `handles` graph edges retain their static
inference authority and source locations. The guide's end-to-end explanation
also uses the visible source in `renderer.js`, `main.js` and `csv.js`; it does
not claim that the complete button-to-file path was observed executing.
The source was formatted before this result was recorded. Re-run analysis
when the example files change.
## Notes browser example
The three-file browser app is in `website/public/examples/notes-web/`.
A dedicated headless Chrome profile opened it at
`http://127.0.0.1:4173/examples/notes-web/`. REA 4.1.0 passively inspected that
existing page with an 8-second window, accessibility text and JSON body shapes.
A separate Playwright controller clicked the button during the capture and
saved the resulting CSV; REA did not perform the click.
- Evidence ID: `ev_7df243651c257e41f525e4225cbca5427467cf1e7b4b5b624ab41ab5717fe9c6`.
- Window: `2026-10-07T09:38:43.492Z` to
`2026-10-07T09:38:51.531Z`.
- One observed `GET` to `/examples/notes-web/notes.json`.
- Status `200`, MIME type `application/json`, script initiator `export.js`.
- Body shape: `/notes` array, `/notes/*/id` number, `/notes/*/title` string.
- The independent browser run downloaded `notes.csv` and displayed
“Exported 2 notes to notes.csv.” CSV construction is explained from source,
not inferred solely from the request capture.
The CLI guide uses a 10-second window to give the reader time to perform the
action. Capture only covers requests after attachment. The example request
path differs under a project prefix such as `/rea/`, while the relative fetch
and export behavior stay the same.
The guide's follow-up CLI commands were also exercised against the real page:
`list-browser-targets` returned its exact page target, and
`inspect-web-page --include-script-sources` returned the loaded `export.js`
source. Discovery Evidence: `ev_31abb33e7707d47625ac909ab9256c705d03a31ee6bcd643fa96730e72469162`. Script capture
Evidence: `ev_aeb777df405c09dc1bbdd4c185a5af2452abaa3cb493683c296c5afa97efbc11`.
Full local outputs and the browser verification harness are under
`/tmp/rea-website-guides/` in the development environment, not website assets.
## Example source digests
| File | SHA-256 |
| -------------------------------------- | ------------------------------------------------------------------ |
| `examples/notes-electron/csv.js` | `6875aeecc1d3e6c1ff5b888a199c26b377d812d5b10e5fb4025e192a206b0018` |
| `examples/notes-electron/index.html` | `a31df9b29eb9d479f913c7806364609019eedd1d19cda386cc24706d5068082d` |
| `examples/notes-electron/main.js` | `cc5b00911bfe428754d94ca40e8c0f354cfaae86f716873423aa5a62b9e0379a` |
| `examples/notes-electron/package.json` | `bfef2a7cba39a989715f56e304ca2578807159f28bdb040b75893bb341caaee7` |
| `examples/notes-electron/preload.js` | `3cb814f2a0822c2fef437ba722374a2eabf6c6795905f41656d9c33fe8d5d9c4` |
| `examples/notes-electron/renderer.js` | `b24aa22ed969c0849dd9da1102935a6b36b96bae46870234bdea10039dd9a5a0` |
| `examples/notes-web/export.js` | `66f5c2501bfca72eb743a1fffbdce9a58d295e69ca66f19562e9b0fa9fc189a6` |
| `examples/notes-web/index.html` | `97bb4729dcd838de8adae7a00eb49df0dbe8a7b73becc1c7d2a6662825013064` |
| `examples/notes-web/notes.json` | `9fc1155e251cfbed09dadca6b195507072fffe3f602564812d644ab27c422282` |

View File

@ -55,3 +55,22 @@ function initializeStepComparisons() {
}
initializeStepComparisons();
function initializeAgentTerminals() {
if (!("IntersectionObserver" in window)) return;
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
entry.target.classList.add("is-visible");
observer.unobserve(entry.target);
});
},
{ threshold: 0.25 },
);
document.querySelectorAll(".agent-terminal").forEach((terminal) => {
observer.observe(terminal);
});
}
initializeAgentTerminals();

View File

@ -707,6 +707,209 @@ pre code {
.small-heading {
margin-top: 1.8rem;
}
.agent-terminal {
margin-block: 1.5rem;
overflow: hidden;
border: 1px solid #303d4a;
border-radius: 6px;
background: #202933;
color: #f1f4f8;
}
.terminal-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: 0.65rem 1rem;
border-bottom: 1px solid #384551;
color: #bdc8d4;
font-size: 0.8rem;
}
.terminal-title {
display: flex;
align-items: center;
gap: 0.7rem;
}
.terminal-dots {
display: flex;
gap: 4px;
}
.terminal-dots i {
width: 6px;
height: 6px;
border-radius: 50%;
background: #81909e;
}
.agent-terminal .copy-button {
color: #d6dee7;
}
.agent-terminal .copy-button:hover {
background: #384551;
color: white;
}
.agent-terminal .terminal-prompt {
margin: 0;
padding: 1.4rem;
color: #f1f4f8;
font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
font-size: 0.9rem;
line-height: 1.9;
}
.prompt-symbol {
color: #9bbfe5;
margin-right: 0.65rem;
}
.terminal-cursor {
display: inline-block;
width: 0.5em;
height: 1em;
margin-left: 0.4em;
vertical-align: -0.12em;
background: #9bbfe5;
}
.agent-terminal.is-visible .terminal-cursor {
animation: terminal-blink 1s step-end 6;
}
@keyframes terminal-blink {
50% {
opacity: 0;
}
}
.workflow-figure {
margin: 1.7rem 0;
border: 1px solid var(--line);
border-radius: 5px;
padding: 1.4rem;
background: #fafbfc;
}
.figure-title {
margin: 0 0 1.3rem;
font-size: 0.9rem;
font-weight: 600;
color: var(--ink);
}
.flow-steps {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 1.9rem;
padding: 0;
margin: 0;
list-style: none;
}
.flow-steps li {
position: relative;
margin: 0;
min-width: 0;
}
.flow-steps li:not(:last-child)::after {
content: "→";
position: absolute;
right: -1.4rem;
top: 2.1rem;
color: #8191a2;
}
.flow-label {
display: block;
margin-bottom: 0.65rem;
color: var(--muted);
font-size: 0.75rem;
}
.flow-node {
display: block;
padding: 0.8rem;
background: white;
border: 1px solid #ced9e5;
border-radius: 4px;
color: var(--accent);
font-size: 0.875rem;
line-height: 1.5;
font-weight: 500;
}
.flow-note {
display: block;
margin-top: 0.65rem;
font-size: 0.8rem;
line-height: 1.55;
color: var(--muted);
}
.flow-result {
margin: 1.3rem 0 0;
padding: 0.9rem 1rem;
border-left: 2px solid var(--accent);
background: #edf2f8;
color: var(--ink);
font-size: 0.9rem;
}
.workflow-figure figcaption {
margin-top: 1rem;
color: var(--muted);
font-size: 0.8rem;
}
.guide-index {
padding-bottom: 3rem;
}
.guide-entry {
display: grid;
grid-template-columns: 160px minmax(0, 1fr);
gap: 2.5rem;
padding-block: 2.5rem;
border-top: 1px solid var(--line);
}
.guide-entry .eyebrow {
margin: 0;
}
.guide-entry h2 {
font-size: 1.75rem;
}
.guide-entry p {
max-width: 660px;
color: var(--muted);
}
.guide-route {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
align-items: center;
margin-bottom: 1.25rem;
font-size: 0.85rem;
color: var(--muted);
}
.guide-route span {
padding: 0.2rem 0.55rem;
background: var(--soft);
border-radius: 3px;
color: #414b55;
}
.example-files {
display: flex;
flex-wrap: wrap;
gap: 0.65rem 1.25rem;
padding: 0;
list-style: none;
font-size: 0.9rem;
}
.example-files li + li {
margin: 0;
}
.example-app {
max-width: 720px;
padding-block: 3.5rem;
}
.example-app h1 {
font-size: 3rem;
}
.example-notes {
padding: 0;
list-style: none;
margin-block: 2rem;
}
.example-notes li {
padding-block: 0.8rem;
border-bottom: 1px solid var(--line);
}
.example-status {
margin-block: 1.5rem 1rem;
}
.sr-only {
position: absolute;
width: 1px;
@ -737,6 +940,17 @@ pre code {
.rea-query-pair {
grid-template-columns: 1fr;
}
.flow-steps {
grid-template-columns: 1fr;
gap: 2rem;
}
.flow-steps li:not(:last-child)::after {
content: "↓";
top: auto;
bottom: -1.7rem;
right: auto;
left: 0.85rem;
}
}
@media (max-width: 640px) {
@ -827,6 +1041,13 @@ pre code {
grid-template-columns: 1fr;
gap: 0.25rem;
}
.guide-entry {
grid-template-columns: 1fr;
gap: 0.7rem;
}
.workflow-figure {
padding: 1rem;
}
.step-selector {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
@ -839,6 +1060,12 @@ pre code {
}
}
@media (prefers-reduced-motion: reduce) {
.agent-terminal.is-visible .terminal-cursor {
animation: none;
}
}
@media (max-width: 360px) {
.header-inner {
flex-wrap: wrap;

View File

@ -0,0 +1,11 @@
function toCsv(notes) {
const quote = (value) => `"${String(value).replaceAll('"', '""')}"`;
return (
[
"id,title",
...notes.map((note) => `${note.id},${quote(note.title)}`),
].join("\n") + "\n"
);
}
module.exports = { toCsv };

View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Notes — Electron analysis example</title>
<script src="renderer.js" defer></script>
</head>
<body>
<h1>Notes</h1>
<button id="export" type="button">Export notes</button>
<p id="status" role="status"></p>
</body>
</html>

View File

@ -0,0 +1,19 @@
const { app, BrowserWindow, ipcMain } = require("electron");
const { writeFileSync } = require("node:fs");
const path = require("node:path");
const { toCsv } = require("./csv.js");
const notes = [{ id: 1, title: "First note" }];
ipcMain.handle("notes:export", () => {
const destination = path.join(app.getPath("downloads"), "notes.csv");
writeFileSync(destination, toCsv(notes), "utf8");
return destination;
});
app.whenReady().then(() => {
const window = new BrowserWindow({
webPreferences: { preload: path.join(__dirname, "preload.js") },
});
window.loadFile("index.html");
});

View File

@ -0,0 +1,6 @@
{
"name": "rea-notes-example",
"version": "1.0.0",
"private": true,
"main": "main.js"
}

View File

@ -0,0 +1,5 @@
const { contextBridge, ipcRenderer } = require("electron");
contextBridge.exposeInMainWorld("notes", {
exportCsv: () => ipcRenderer.invoke("notes:export"),
});

View File

@ -0,0 +1,4 @@
document.querySelector("#export").addEventListener("click", async () => {
const destination = await window.notes.exportCsv();
document.querySelector("#status").textContent = `Saved to ${destination}`;
});

View File

@ -0,0 +1,24 @@
document.querySelector("#export").addEventListener("click", async () => {
const status = document.querySelector("#status");
try {
const response = await fetch("./notes.json");
if (!response.ok)
throw new Error(`Notes request failed: ${response.status}`);
const { notes } = await response.json();
const quote = (value) => `"${String(value).replaceAll('"', '""')}"`;
const csv =
[
"id,title",
...notes.map((note) => `${note.id},${quote(note.title)}`),
].join("\n") + "\n";
const url = URL.createObjectURL(new Blob([csv], { type: "text/csv" }));
const link = document.createElement("a");
link.href = url;
link.download = "notes.csv";
link.click();
window.setTimeout(() => URL.revokeObjectURL(url), 1000);
status.textContent = `Exported ${notes.length} notes to notes.csv.`;
} catch (error) {
status.textContent = error.message;
}
});

View File

@ -0,0 +1,35 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Notes — REA browser example</title>
<link rel="icon" href="../../assets/favicon.svg" type="image/svg+xml" />
<link rel="stylesheet" href="../../assets/styles.css" />
<script src="export.js" defer></script>
</head>
<body>
<main class="container example-app">
<p class="breadcrumb">
<a href="../../guides/browser/">Browser observation guide</a> / Example
</p>
<p class="eyebrow">A small app to inspect</p>
<h1>Notes</h1>
<p class="lede">
Two notes. One CSV export. Observe what happens when you click.
</p>
<ul class="example-notes">
<li>First note</li>
<li>Follow the export</li>
</ul>
<button class="button" type="button" id="export">Export notes</button>
<p class="example-status" id="status" role="status" aria-live="polite">
Ready to export.
</p>
<p class="caption">
This example loads <code>notes.json</code> and creates a CSV in your
browser.
</p>
</main>
</body>
</html>

View File

@ -0,0 +1,6 @@
{
"notes": [
{ "id": 1, "title": "First note" },
{ "id": 2, "title": "Follow the export" }
]
}

View File

@ -24,7 +24,7 @@
<nav class="navigation" aria-label="Main navigation">
<a href="../showcase/dx-ball/">DX-Ball</a
><a class="nav-start" href="./" aria-current="page">Get started</a
><a href="https://github.com/morluto/rea/tree/main/docs">Docs</a
><a href="../guides/">Guides</a
><a href="https://github.com/morluto/rea">GitHub</a>
</nav>
</div>
@ -39,10 +39,6 @@
</p>
<p class="requirements">
You'll need Node.js 22 (22.19+), 24 (24.11+) or 26+, and npm.
<a
href="https://github.com/morluto/rea/blob/main/docs/installation.md"
>Full installation guide</a
>
</p>
</div>
<div class="article-layout">
@ -59,11 +55,9 @@
<section class="article-section" id="agent">
<h2>With your agent</h2>
<p>
Run setup to connect REA to Claude Code, Codex, Cursor or another
<a
href="https://github.com/morluto/rea/blob/main/docs/installation.md#supported-agents"
>supported client</a
>.
Run setup to connect REA to your coding agent. Client choices
include Claude Code, Codex, Cursor, Gemini CLI, Windsurf and other
supported MCP clients.
</p>
<div class="code-block">
<div class="code-header">
@ -154,8 +148,7 @@
<pre><code id="install-cli">npm install --global rea-agents</code></pre>
</div>
<p>
<a
href="https://github.com/morluto/rea/blob/main/docs/javascript-application-workflows.md"
<a href="../guides/javascript/"
>Continue with the JavaScript application guide</a
>
</p>
@ -164,47 +157,111 @@
<section class="article-section" id="native">
<h2>Working with native binaries</h2>
<p>
Choose a deep-analysis provider for your target and host. If you
already use one of these tools, start with its connection guide.
REA connects your agent to a local analysis provider. Choose the
tool you already use, configure its connection, then ask about
your executable or library.
</p>
<div class="provider-list">
<div class="provider">
<h3>Ghidra</h3>
<div>
<p>
Use an existing Ghidra installation and JDK. Setup checks
their paths and records them for your selected clients.
Use an existing Ghidra 12.1.x installation and a 64-bit JDK
21 or newer on Linux x64 or macOS x64/arm64. Set the paths
before running setup:
</p>
<a
href="https://github.com/morluto/rea/blob/main/docs/installation.md#ghidra"
>Ghidra setup and supported hosts</a
>
<details class="evidence-details">
<summary>Connect your Ghidra installation</summary>
<div class="code-block">
<div class="code-header">
<span>Ghidra connection · macOS / Linux</span
><button
class="copy-button"
type="button"
data-copy="ghidra-connect"
aria-label="Copy ghidra connection · macos / linux"
>
Copy
</button>
</div>
<pre><code id="ghidra-connect">export GHIDRA_INSTALL_DIR=/absolute/path/to/ghidra_12.1.4_PUBLIC
export JAVA_HOME=/absolute/path/to/jdk-21
npx -y rea-agents@latest doctor --provider ghidra --json
npx rea-agents@latest setup</code></pre>
</div>
<p>
Replace both paths with your existing installations. Setup
checks them and records valid settings for your selected
client. On macOS, use a Ghidra package with the native
decompiler matching your host architecture.
</p>
</details>
</div>
</div>
<div class="provider">
<h3>Hopper</h3>
<div>
<p>
Connect Hopper on macOS or Linux. Setup can also install it
after you review and approve the installation plan.
Connect Hopper on macOS or Linux. Run the setup command
above; it detects an existing installation or offers an
installation plan for your approval.
</p>
<p>
On macOS, complete Hopper's first-run choice of demo mode or
license activation before an unattended analysis.
</p>
<a
href="https://github.com/morluto/rea/blob/main/docs/installation.md#hopper"
>Hopper installation</a
>
</div>
</div>
<div class="provider">
<h3>IDA</h3>
<div>
<p>
Reuse an existing IDA MCP registration for read-only
analysis through an attached GUI or headless database.
The verified IDA setups use Windows, with an existing MCP
registration for an attached GUI or a headless database.
Linux and macOS headless analysis remains unverified.
</p>
<a
href="https://github.com/morluto/rea/blob/main/docs/ida-provider.md"
>IDA connection guide</a
>
<details class="evidence-details">
<summary>Connect an existing IDA MCP registration</summary>
<p>
Keep the upstream server's working <code>command</code>,
<code>args</code> and optional <code>env</code> in a JSON
file. An existing <code>mcpServers</code> object with an
<code>ida-pro-mcp</code> entry is also accepted. Point REA
at that file before setup:
</p>
<div class="code-block">
<div class="code-header">
<span>IDA connection · Windows PowerShell</span
><button
class="copy-button"
type="button"
data-copy="ida-connect"
aria-label="Copy IDA connection for Windows PowerShell"
>
Copy
</button>
</div>
<pre><code id="ida-connect">$env:REA_IDA_MCP_CONFIG = "C:/analysis/ida-mcp.json"
$env:REA_ANALYSIS_PROVIDER = "ida"
npx -y rea-agents@latest doctor --provider ida --json
npx rea-agents@latest setup</code></pre>
</div>
<p>
The attached profile uses upstream legacy 1.4.0 tools with
the original binary already open in IDA. The headless
profile uses the upstream database-supervisor API and a
registration with <code>"mode": "headless"</code>; real
verification covers an IDA 9.3 Windows x64 supervisor.
Doctor checks the registration without starting IDA.
</p>
<p>
Give REA the original executable path, with
<code>--provider ida</code> in a CLI query. For an agent,
include <code>REA_IDA_MCP_CONFIG</code> in the REA
registration's environment and select IDA when opening the
binary.
</p>
</details>
</div>
</div>
</div>
@ -228,8 +285,13 @@
</div>
<p>
Replace <code>main</code> with the function name or address you
want to inspect. The first query imports and analyzes the target,
so it can take longer than later requests.
want to inspect. Each CLI invocation imports and analyzes the
target in its own session. Your agent's MCP queries reuse the
imported binary while that session is open.
</p>
<p>
<a href="../guides/native/">Follow a native analysis example</a>
to see how the function result leads to a recovered calculation.
</p>
</section>
@ -273,12 +335,10 @@
<p>The report gives the failed check and a suggested next step.</p>
<ul class="reading-list">
<li>
<a
href="https://github.com/morluto/rea/blob/main/docs/installation.md"
>Installation and troubleshooting</a
<a href="../guides/">Choose an investigation guide</a
><span
>Client configuration, versions and provider
requirements.</span
>Worked examples for a binary, an Electron app and a
browser.</span
>
</li>
<li>

View File

@ -0,0 +1,339 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Browser observation — REA</title>
<meta
name="description"
content="Observe a Notes export with REA: select a browser page, capture a click, inspect the request and follow the export script."
/>
<meta name="theme-color" content="#ffffff" />
<link rel="icon" href="../../assets/favicon.svg" type="image/svg+xml" />
<link rel="stylesheet" href="../../assets/styles.css" />
<script src="../../assets/main.js" defer></script>
</head>
<body>
<a class="skip-link" href="#main">Skip to content</a>
<header class="site-header">
<div class="container header-inner">
<a class="brand" href="../../" aria-label="REA home"
><span class="brand-name">REA</span
><span class="brand-caption">Reverse Engineer Anything</span></a
>
<nav class="navigation" aria-label="Main navigation">
<a href="../../showcase/dx-ball/">DX-Ball</a
><a class="nav-start" href="../../get-started/">Get started</a
><a href="../">Guides</a
><a href="https://github.com/morluto/rea">GitHub</a>
</nav>
</div>
</header>
<main id="main" class="container">
<div class="page-heading">
<p class="breadcrumb">
<a href="../../">Home</a> / <a href="../">Guides</a> / Browser
observation
</p>
<h1>See what happens<br />when you click.</h1>
<p class="lede">
Use a small browser app to connect an action, its network request and
the JavaScript that builds a download.
</p>
</div>
<div class="article-layout">
<nav class="contents" aria-label="On this page">
<p>In this guide</p>
<ul>
<li><a href="#example">The example</a></li>
<li><a href="#prepare">Open the browser</a></li>
<li><a href="#ask">Ask your agent</a></li>
<li><a href="#capture">Capture from CLI</a></li>
<li><a href="#result">Read and follow</a></li>
</ul>
</nav>
<article class="article" aria-label="Browser observation">
<section class="article-section" id="example">
<h2>What happens when you click Export?</h2>
<p>
The
<a href="../../examples/notes-web/">Notes browser example</a> has
an <strong>Export notes</strong> button. Clicking it loads
<code>notes.json</code>, formats the two notes as CSV, and starts
a download.
</p>
<p>
We want to see which request the click makes, then use the script
to understand how the response becomes a file.
</p>
<figure class="workflow-figure">
<p class="figure-title">One action, captured while it happens</p>
<ol class="flow-steps">
<li>
<span class="flow-label">01 · You</span
><span class="flow-node">Click Export notes</span
><span class="flow-note"
>Perform the action in your browser while the capture is
running.</span
>
</li>
<li>
<span class="flow-label">02 · REA</span
><span class="flow-node">Record the request</span
><span class="flow-note"
>Capture the request URL, response status and JSON structure
from the selected page.</span
>
</li>
<li>
<span class="flow-label">03 · Your agent</span
><span class="flow-node">Read the export code</span
><span class="flow-note"
>Connect the observed request to
<code>fetch("./notes.json")</code> in
<code>export.js</code>.</span
>
</li>
</ol>
<p class="flow-result">
<code>GET notes.json → 200 → notes[] → CSV</code>
</p>
<figcaption>
The request and response metadata come from the capture. CSV
construction is explained by reading the script.
</figcaption>
</figure>
</section>
<section class="article-section" id="prepare">
<h2>Open an observable browser</h2>
<p>
Start a Chrome-family browser with a dedicated profile and a local
debugging port. On Linux, for this site's local preview:
</p>
<div class="code-block">
<div class="code-header">
<span>Start the example browser</span
><button
class="copy-button"
type="button"
data-copy="browser-start"
aria-label="Copy start the example browser"
>
Copy
</button>
</div>
<pre><code id="browser-start">google-chrome \
--remote-debugging-port=9222 \
--user-data-dir=/tmp/rea-notes-browser \
http://127.0.0.1:4173/examples/notes-web/</code></pre>
</div>
<p>
Use your installed browser's executable path on macOS or Windows.
The page URL should point to your local preview or
<a href="../../examples/notes-web/">the hosted Notes example</a>.
</p>
<p>
REA connects to this existing browser. You keep control of the
page, perform the action, and close the browser when finished.
</p>
</section>
<section class="article-section" id="ask">
<h2>Ask your agent to watch one action</h2>
<div class="agent-terminal">
<div class="terminal-header">
<span class="terminal-title"
><span class="terminal-dots" aria-hidden="true"
><i></i><i></i><i></i></span
>Your coding agent</span
><button
class="copy-button"
type="button"
data-copy="browser-prompt"
aria-label="Copy example prompt"
>
Copy
</button>
</div>
<p class="terminal-prompt">
<span class="prompt-symbol" aria-hidden="true">›</span
><span id="browser-prompt"
>Use REA to inspect this page while I click Export notes. What
request does it make, and how is the CSV built?</span
><span class="terminal-cursor" aria-hidden="true"></span>
</p>
</div>
<p class="caption">
Example prompt. Give your agent the debugging endpoint,
<code>http://127.0.0.1:9222</code>, and the page URL. Let it start
the capture before you click.
</p>
<p>
Your agent discovers the page target, starts an observation
window, and reads the resulting evidence. It can then inspect the
script behind the export.
</p>
</section>
<section class="article-section" id="capture">
<h2>Or capture from the terminal</h2>
<p>Find the page's target ID:</p>
<div class="code-block">
<div class="code-header">
<span>Find the page</span
><button
class="copy-button"
type="button"
data-copy="browser-targets"
aria-label="Copy find the page"
>
Copy
</button>
</div>
<pre><code id="browser-targets">npx -y rea-agents@latest list-browser-targets \
http://127.0.0.1:9222 --json</code></pre>
</div>
<p>
Choose the target with the Notes example URL. Replace
<code>TARGET_ID</code> below with its ID, then run:
</p>
<div class="code-block">
<div class="code-header">
<span>Capture the next ten seconds</span
><button
class="copy-button"
type="button"
data-copy="browser-capture"
aria-label="Copy capture the next ten seconds"
>
Copy
</button>
</div>
<pre><code id="browser-capture">npx -y rea-agents@latest inspect-web-page \
http://127.0.0.1:9222 TARGET_ID \
--observation-ms 10000 \
--include-accessibility-text \
--include-json-body-shapes --json</code></pre>
</div>
<p>
Let the command connect for a few seconds, then click
<strong>Export notes</strong> in that browser. The result arrives
after the observation window ends.
</p>
<p>
The captured result includes the selected page, network activity
during that window, and the shape of the JSON response. Earlier
requests are outside this capture.
</p>
</section>
<section class="article-section" id="result">
<h2>Read the result, then follow the code</h2>
<div class="evidence-table-wrap">
<table class="evidence-table">
<caption>
Recorded with REA 4.1.0 on the local Notes example
</caption>
<thead>
<tr>
<th>Captured fact</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td>Request</td>
<td><code>GET /examples/notes-web/notes.json</code></td>
</tr>
<tr>
<td>Response</td>
<td><code>200 · application/json</code></td>
</tr>
<tr>
<td>Request initiator</td>
<td><code>export.js</code> · script</td>
</tr>
<tr>
<td>Response structure</td>
<td>
<code>notes[]</code> with <code>id</code> and
<code>title</code> fields
</td>
</tr>
</tbody>
</table>
</div>
<div class="code-block">
<div class="code-header">
<span>Read the loaded scripts</span
><button
class="copy-button"
type="button"
data-copy="browser-scripts"
aria-label="Copy read the loaded scripts"
>
Copy
</button>
</div>
<pre><code id="browser-scripts">npx -y rea-agents@latest inspect-web-page \
http://127.0.0.1:9222 TARGET_ID \
--include-script-sources --json</code></pre>
</div>
<p>
The request identifies the data source, and REA records
<code>export.js</code> as its initiator. Read the captured script
source to see how the data is used:
</p>
<div class="code-block">
<div class="code-header">
<span>export.js · example source excerpt</span>
</div>
<pre><code>const response = await fetch(&quot;./notes.json&quot;);
const { notes } = await response.json();
// Format each note as a CSV row.
const url = URL.createObjectURL(new Blob([csv], { type: &quot;text/csv&quot; }));
link.href = url;
link.download = &quot;notes.csv&quot;;
link.click();</code></pre>
</div>
<p>
The download is constructed in the page using the JSON response.
The CSV formatting step comes from the script; the network capture
establishes that this click requested <code>notes.json</code>.
</p>
<p>
<a href="../../examples/notes-web/">Open the example</a> ·
<a href="../../examples/notes-web/export.js" download
>Download export.js</a
>
·
<a href="../../examples/notes-web/notes.json" download
>Download notes.json</a
>
</p>
<blockquote class="prompt">
<p>
Find the script that requests <code>notes.json</code>. How does
it escape titles containing commas or quotation marks?
</p>
</blockquote>
<p>
For an installed JavaScript or Electron app, continue with the
<a href="../javascript/">application workflow</a> to follow files
and process boundaries.
</p>
</section>
</article>
</div>
</main>
<footer class="site-footer">
<div class="container footer-inner">
<p>REA · Reverse Engineer Anything</p>
<div class="footer-links">
<a href="https://github.com/morluto/rea">Source</a
><a href="https://github.com/morluto/rea/blob/main/LICENSE"
>MIT license</a
><a href="https://discord.gg/GkcryMnJDM">Community</a>
</div>
</div>
</footer>
<p class="sr-only" id="copy-status" role="status" aria-live="polite"></p>
</body>
</html>

View File

@ -0,0 +1,119 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Guides — REA</title>
<meta
name="description"
content="Learn REA through short worked examples: a native calculation, an Electron CSV export and a browser request."
/>
<meta name="theme-color" content="#ffffff" />
<link rel="icon" href="../assets/favicon.svg" type="image/svg+xml" />
<link rel="stylesheet" href="../assets/styles.css" />
<script src="../assets/main.js" defer></script>
</head>
<body>
<a class="skip-link" href="#main">Skip to content</a>
<header class="site-header">
<div class="container header-inner">
<a class="brand" href="../" aria-label="REA home"
><span class="brand-name">REA</span
><span class="brand-caption">Reverse Engineer Anything</span></a
>
<nav class="navigation" aria-label="Main navigation">
<a href="../showcase/dx-ball/">DX-Ball</a
><a class="nav-start" href="../get-started/">Get started</a
><a href="./" aria-current="page">Guides</a
><a href="https://github.com/morluto/rea">GitHub</a>
</nav>
</div>
</header>
<main id="main" class="container">
<div class="page-heading">
<p class="breadcrumb"><a href="../">Home</a> / Guides</p>
<h1>Start with<br />an example.</h1>
<p class="lede">
Follow an example from the first prompt to the evidence. Then use the
same approach with your own app.
</p>
</div>
<div class="guide-index">
<section class="guide-entry">
<p class="eyebrow">Native binaries</p>
<div>
<h2>Recover a sound-pan calculation</h2>
<p>
Start with a Windows game executable. Inspect instructions, follow
a caller, read numeric constants and check the recovered C.
</p>
<div class="guide-route">
<span>Instructions</span> <b aria-hidden="true">→</b>
<span>Caller</span> <b aria-hidden="true">→</b>
<span>Constants</span> <b aria-hidden="true">→</b>
<span>C function</span>
</div>
<a class="text-link" href="native/">Native analysis guide</a>
</div>
</section>
<section class="guide-entry">
<p class="eyebrow">JavaScript &amp; Electron</p>
<div>
<h2>Follow a CSV export</h2>
<p>
A small Notes app spreads one feature across the renderer, preload
and main process. Use REA to connect the API, IPC channel and file
writer.
</p>
<div class="guide-route">
<span>Button</span> <b aria-hidden="true">→</b>
<span>Preload API</span> <b aria-hidden="true">→</b>
<span>IPC</span> <b aria-hidden="true">→</b> <span>CSV file</span>
</div>
<a class="text-link" href="javascript/">Application workflows</a>
</div>
</section>
<section class="guide-entry">
<p class="eyebrow">Browser behavior</p>
<div>
<h2>Observe a click</h2>
<p>
Open a working Notes example, capture an export request and find
the code that turns its response into a download.
</p>
<div class="guide-route">
<span>Your click</span> <b aria-hidden="true">→</b>
<span>Captured request</span> <b aria-hidden="true">→</b>
<span>Export script</span>
</div>
<a class="text-link" href="browser/">Browser observation guide</a>
</div>
</section>
<section class="guide-entry">
<p class="eyebrow">First investigation</p>
<div>
<h2>Connect your agent, then ask.</h2>
<p>
Each guide starts with a plain-language question and follows a
concrete example. If you haven't connected REA yet, setup takes
you through the client and provider choices.
</p>
<a class="text-link" href="../get-started/">Get started with REA</a>
</div>
</section>
</div>
</main>
<footer class="site-footer">
<div class="container footer-inner">
<p>REA · Reverse Engineer Anything</p>
<div class="footer-links">
<a href="https://github.com/morluto/rea">Source</a
><a href="https://github.com/morluto/rea/blob/main/LICENSE"
>MIT license</a
><a href="https://discord.gg/GkcryMnJDM">Community</a>
</div>
</div>
</footer>
<p class="sr-only" id="copy-status" role="status" aria-live="polite"></p>
</body>
</html>

View File

@ -0,0 +1,315 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>JavaScript &amp; Electron — REA</title>
<meta
name="description"
content="Trace a Notes CSV export through a renderer API, preload and IPC handler with REA. Includes downloadable example files and a verified analysis result."
/>
<meta name="theme-color" content="#ffffff" />
<link rel="icon" href="../../assets/favicon.svg" type="image/svg+xml" />
<link rel="stylesheet" href="../../assets/styles.css" />
<script src="../../assets/main.js" defer></script>
</head>
<body>
<a class="skip-link" href="#main">Skip to content</a>
<header class="site-header">
<div class="container header-inner">
<a class="brand" href="../../" aria-label="REA home"
><span class="brand-name">REA</span
><span class="brand-caption">Reverse Engineer Anything</span></a
>
<nav class="navigation" aria-label="Main navigation">
<a href="../../showcase/dx-ball/">DX-Ball</a
><a class="nav-start" href="../../get-started/">Get started</a
><a href="../">Guides</a
><a href="https://github.com/morluto/rea">GitHub</a>
</nav>
</div>
</header>
<main id="main" class="container">
<div class="page-heading">
<p class="breadcrumb">
<a href="../../">Home</a> / <a href="../">Guides</a> / JavaScript
&amp; Electron
</p>
<h1>Follow a feature<br />across the app.</h1>
<p class="lede">
Use a CSV export to see how REA connects JavaScript files, exposed
APIs and Electron process boundaries.
</p>
</div>
<div class="article-layout">
<nav class="contents" aria-label="On this page">
<p>In this guide</p>
<ul>
<li><a href="#example">The example</a></li>
<li><a href="#ask">Ask your agent</a></li>
<li><a href="#follow">Connect the pieces</a></li>
<li><a href="#try">Try the example</a></li>
<li><a href="#next">Continue investigating</a></li>
</ul>
</nav>
<article class="article" aria-label="JavaScript &amp; Electron">
<section class="article-section" id="example">
<h2>Where does “Export notes” go?</h2>
<p>
This guide uses a small Electron app. Clicking
<strong>Export notes</strong> calls an API in the page, crosses
into the main process, and writes a CSV. The behavior is spread
across several files.
</p>
<figure class="workflow-figure">
<p class="figure-title">A feature across two processes</p>
<ol class="flow-steps">
<li>
<span class="flow-label">Renderer → preload</span
><span class="flow-node"><code>notes.exportCsv()</code></span
><span class="flow-note"
><code>renderer.js</code> calls the API exposed by
<code>preload.js</code>.</span
>
</li>
<li>
<span class="flow-label">Between processes</span
><span class="flow-node"><code>notes:export</code></span
><span class="flow-note"
>The preload invokes an IPC channel: a named message to the
main process.</span
>
</li>
<li>
<span class="flow-label">Main process</span
><span class="flow-node">An export handler</span
><span class="flow-note"
><code>main.js</code> handles that channel and calls the CSV
writer.</span
>
</li>
</ol>
<p class="flow-result">
<code>csv.js</code> formats the notes →
<code>main.js</code> writes <code>downloads/notes.csv</code>.
</p>
<figcaption>
REA matches the literal IPC call to its handler and identifies
the exposed API. The path is inferred from code; a runtime
capture can check what executes.
</figcaption>
</figure>
</section>
<section class="article-section" id="ask">
<h2>Give your agent one feature</h2>
<p>
<a href="../../get-started/#agent">Connect your agent to REA</a>
and give it the app directory or ASAR file:
</p>
<div class="agent-terminal">
<div class="terminal-header">
<span class="terminal-title"
><span class="terminal-dots" aria-hidden="true"
><i></i><i></i><i></i></span
>Your coding agent</span
><button
class="copy-button"
type="button"
data-copy="javascript-prompt"
aria-label="Copy example prompt"
>
Copy
</button>
</div>
<p class="terminal-prompt">
<span class="prompt-symbol" aria-hidden="true">›</span
><span id="javascript-prompt"
>Use REA to find what happens when I export notes in this app.
Where is the CSV created?</span
><span class="terminal-cursor" aria-hidden="true"></span>
</p>
</div>
<p class="caption">
Example prompt. Include the absolute path to the application
folder or <code>app.asar</code>.
</p>
<p>
REA reads the application files and builds a map of entrypoints,
modules and process boundaries. Your agent uses that map to narrow
the investigation to the export feature.
</p>
</section>
<section class="article-section" id="follow">
<h2>Connect the pieces</h2>
<h3>The page calls a small API</h3>
<div class="code-block">
<div class="code-header">
<span>renderer.js · example source</span>
</div>
<pre><code>document.querySelector(&quot;#export&quot;).addEventListener(&quot;click&quot;, async () =&gt; {
const destination = await window.notes.exportCsv();
document.querySelector(&quot;#status&quot;).textContent = `Saved to ${destination}`;
});</code></pre>
</div>
<p>
The page knows the method name. The implementation lives in the
preload, which exposes it to the renderer.
</p>
<div class="code-block">
<div class="code-header">
<span>preload.js · example source</span>
</div>
<pre><code>contextBridge.exposeInMainWorld(&quot;notes&quot;, {
exportCsv: () =&gt; ipcRenderer.invoke(&quot;notes:export&quot;),
});</code></pre>
</div>
<p>
REA records <code>notes.exportCsv</code> and the literal channel
<code>notes:export</code>. It pairs that invocation with the
single matching handler in the main process.
</p>
<div class="code-block">
<div class="code-header">
<span>main.js · example source</span>
</div>
<pre><code>ipcMain.handle(&quot;notes:export&quot;, () =&gt; {
const destination = path.join(app.getPath(&quot;downloads&quot;), &quot;notes.csv&quot;);
writeFileSync(destination, toCsv(notes), &quot;utf8&quot;);
return destination;
});</code></pre>
</div>
<p>
Reading the handler answers the original question:
<code>csv.js</code> formats the notes, and the main process writes
<code>notes.csv</code> into the user's Downloads folder.
</p>
<div class="code-block">
<div class="code-header">
<span>REA result · summary.ipc excerpt</span>
</div>
<pre><code>{
&quot;literal_channels&quot;: 1,
&quot;renderer_transmissions&quot;: 1,
&quot;main_handlers&quot;: 1,
&quot;paired_renderer_transmissions&quot;: 1,
&quot;ambiguous_renderer_transmissions&quot;: 0
}</code></pre>
</div>
<p class="caption">
Recorded by REA 4.1.0 on the downloadable example below. Source
locations accompany the API, channel and handler findings.
</p>
</section>
<section class="article-section" id="try">
<h2>Analyze the example yourself</h2>
<p>
Save these six files together in a folder named
<code>notes-example</code>. Static analysis needs Node.js and npm;
you can inspect the code without installing or launching Electron.
</p>
<ul class="example-files">
<li>
<a href="../../examples/notes-electron/package.json" download
>package.json</a
>
</li>
<li>
<a href="../../examples/notes-electron/main.js" download
>main.js</a
>
</li>
<li>
<a href="../../examples/notes-electron/preload.js" download
>preload.js</a
>
</li>
<li>
<a href="../../examples/notes-electron/renderer.js" download
>renderer.js</a
>
</li>
<li>
<a href="../../examples/notes-electron/csv.js" download
>csv.js</a
>
</li>
<li>
<a href="../../examples/notes-electron/index.html" download
>index.html</a
>
</li>
</ul>
<div class="code-block">
<div class="code-header">
<span>Static application analysis</span
><button
class="copy-button"
type="button"
data-copy="javascript-command"
aria-label="Copy static application analysis"
>
Copy
</button>
</div>
<pre><code id="javascript-command">npx -y rea-agents@latest analyze-javascript-application \
/absolute/path/to/notes-example --json</code></pre>
</div>
<p>
Replace the path with the folder you saved. Look for
<code>normalized_result.summary.ipc</code> and the source
locations in <code>normalized_result.graph</code>. The example has
one channel and one paired invocation.
</p>
<p>
For your own app, use the same command with its directory or ASAR
path. An agent can follow a literal string, API name or IPC
channel with REA's feature tracing tools.
</p>
</section>
<section class="article-section" id="next">
<h2>Ask the next question</h2>
<blockquote class="prompt">
<p>
Follow <code>notes:export</code> to its handler. Which inputs
reach the CSV writer, and what happens if writing the file
fails?
</p>
</blockquote>
<p>
Use the source locations to inspect those details. Computed
channel names and ambiguous handlers can remain unresolved;
runtime observations give you another way to investigate them.
</p>
<ul class="reading-list">
<li>
<a href="../browser/">Observe a browser export</a
><span
>Capture a real click and see which request it makes.</span
>
</li>
<li>
<a href="../native/">Follow a native function</a
><span
>Continue into a binary when a feature depends on native
code.</span
>
</li>
</ul>
</section>
</article>
</div>
</main>
<footer class="site-footer">
<div class="container footer-inner">
<p>REA · Reverse Engineer Anything</p>
<div class="footer-links">
<a href="https://github.com/morluto/rea">Source</a
><a href="https://github.com/morluto/rea/blob/main/LICENSE"
>MIT license</a
><a href="https://discord.gg/GkcryMnJDM">Community</a>
</div>
</div>
</footer>
<p class="sr-only" id="copy-status" role="status" aria-live="polite"></p>
</body>
</html>

View File

@ -0,0 +1,276 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Native analysis — REA</title>
<meta
name="description"
content="Recover a native calculation with REA: inspect instructions, follow a caller, read constants and check reconstructed C."
/>
<meta name="theme-color" content="#ffffff" />
<link rel="icon" href="../../assets/favicon.svg" type="image/svg+xml" />
<link rel="stylesheet" href="../../assets/styles.css" />
<script src="../../assets/main.js" defer></script>
</head>
<body>
<a class="skip-link" href="#main">Skip to content</a>
<header class="site-header">
<div class="container header-inner">
<a class="brand" href="../../" aria-label="REA home"
><span class="brand-name">REA</span
><span class="brand-caption">Reverse Engineer Anything</span></a
>
<nav class="navigation" aria-label="Main navigation">
<a href="../../showcase/dx-ball/">DX-Ball</a
><a class="nav-start" href="../../get-started/">Get started</a
><a href="../">Guides</a
><a href="https://github.com/morluto/rea">GitHub</a>
</nav>
</div>
</header>
<main id="main" class="container">
<div class="page-heading">
<p class="breadcrumb">
<a href="../../">Home</a> / <a href="../">Guides</a> / Native analysis
</p>
<h1>Recover a calculation.<br />Follow the evidence.</h1>
<p class="lede">
Use DX-Ball’s sound panning to see how an agent moves from a binary to
an explanation and a checked C function.
</p>
</div>
<div class="article-layout">
<nav class="contents" aria-label="On this page">
<p>In this guide</p>
<ul>
<li><a href="#example">The example</a></li>
<li><a href="#ask">Ask your agent</a></li>
<li><a href="#follow">Follow the evidence</a></li>
<li><a href="#result">Recover and check</a></li>
<li><a href="#try">Use your own binary</a></li>
</ul>
</nav>
<article class="article" aria-label="Native analysis">
<section class="article-section" id="example">
<h2>A position becomes a sound</h2>
<p>
In DX-Ball, a brick hit supplies a horizontal screen position. The
sound code turns it into a left/right panning value. We want to
recover that calculation from the executable.
</p>
<p>
The starting function, <code>0x00406400</code>, has a short
decompiler result: a call to <code>__ftol()</code>. REA also
returns its instructions and callers, which give the agent a way
to investigate the missing arithmetic.
</p>
<figure class="workflow-figure">
<p class="figure-title">
Three pieces of evidence, one calculation
</p>
<ol class="flow-steps">
<li>
<span class="flow-label">01 · Instructions</span
><span class="flow-node">An integer input</span
><span class="flow-note"
>A stack read supplies the value used by the floating-point
instructions.</span
>
</li>
<li>
<span class="flow-label">02 · Caller</span
><span class="flow-node">A screen coordinate</span
><span class="flow-note"
>The brick-hit caller passes
<code>20 + 30 × tile_x</code>.</span
>
</li>
<li>
<span class="flow-label">03 · Data bytes</span
><span class="flow-node">The numeric constants</span
><span class="flow-note"
>Referenced bytes decode to <code>1.5625</code>,
<code>500.0</code> and a scale value.</span
>
</li>
</ol>
<p class="flow-result">
<code>pan = (x × 1.5625 − 500.0) × scale</code>
</p>
<figcaption>
REA supplies the instructions, call relationship and bytes. The
agent interprets them together.
</figcaption>
</figure>
</section>
<section class="article-section" id="ask">
<h2>Ask about the behavior</h2>
<p>
After
<a href="../../get-started/#agent">connecting REA to your agent</a
>, provide the local executable and a question:
</p>
<div class="agent-terminal">
<div class="terminal-header">
<span class="terminal-title"
><span class="terminal-dots" aria-hidden="true"
><i></i><i></i><i></i></span
>Your coding agent</span
><button
class="copy-button"
type="button"
data-copy="native-prompt"
aria-label="Copy example prompt"
>
Copy
</button>
</div>
<p class="terminal-prompt">
<span class="prompt-symbol" aria-hidden="true">›</span
><span id="native-prompt"
>Use REA to find how DX-Ball calculates sound panning. Explain
the calculation and show the code.</span
><span class="terminal-cursor" aria-hidden="true"></span>
</p>
</div>
<p class="caption">
Example prompt. Give your agent the path to your local DX-Ball
1.07 executable.
</p>
<p>
You can start with a feature name. Your agent uses REA to locate
relevant functions, then follows the evidence with more focused
queries.
</p>
</section>
<section class="article-section" id="follow">
<h2>Follow what REA returns</h2>
<h3>Read the input</h3>
<div class="code-block">
<div class="code-header">
<span>REA instruction excerpt · 0x406409</span>
</div>
<pre><code>MOV EAX, dword ptr [EBP + 0x8]
MOV dword ptr [EBP + -0xc], EAX
FILD dword ptr [EBP + -0xc]</code></pre>
</div>
<p>
The instructions load an integer argument and convert it for
floating-point arithmetic. This is the input absent from the first
pseudocode view.
</p>
<h3 class="small-heading">Follow the caller</h3>
<div class="code-block">
<div class="code-header">
<span>REA caller excerpt · 0x411f5b</span>
</div>
<pre><code>ADD EAX, 0x14
PUSH EAX
CALL 0x00406400</code></pre>
</div>
<p>
REA identifies the brick-hit caller. Its earlier instructions
multiply the tile coordinate by 30; adding <code>0x14</code> (20)
gives the brick's screen position.
</p>
<h3 class="small-heading">Read the referenced constants</h3>
<div class="code-block">
<div class="code-header">
<span
>REA byte results · interpreted as little-endian doubles</span
>
</div>
<pre><code>0x420068 000000000000f93f → 1.5625
0x420070 0000000000407f40 → 500.0
0x4210a0 000000000000f03f → 1.0</code></pre>
</div>
<p>
The arithmetic references these addresses. Reading the bytes
supplies the actual values; the instruction order tells the agent
how they are used.
</p>
</section>
<section class="article-section" id="result">
<h2>Write and check the recovered function</h2>
<div class="code-block">
<div class="code-header"><span>Recovered C · excerpt</span></div>
<pre><code>pan = (double)x;
pan = pan * 1.5625;
pan = pan - 500.0;
pan = pan * dxball_pan_scale;
return (DxBallInt)pan;</code></pre>
</div>
<p>
With a scale of 1.0, screen positions 0, 320 and 640 map to −500,
0 and 500 before integer conversion. The calculation puts the
center of the screen at the center of the stereo field.
</p>
<p>
The agent used REA's findings to write the C. Separate checks
compared 3,205 inputs against the original x86 function and
reproduced all 63 compiled bytes of the complete function.
</p>
<p>
<a href="../../showcase/dx-ball/#sound-pan"
>See the full investigation and assembly-to-C comparison</a
>
</p>
</section>
<section class="article-section" id="try">
<h2>Try it with your own binary</h2>
<p>
<a href="../../get-started/#native"
>Connect a native analysis provider</a
>, then give your agent a local executable or library and a
feature to investigate. Useful starting questions include “How is
this file parsed?” or “Which function handles this menu action?”
</p>
<p>
If you already know a function name or address, you can query it
from the terminal. For a configured Ghidra installation:
</p>
<div class="code-block">
<div class="code-header">
<span>Function analysis</span
><button
class="copy-button"
type="button"
data-copy="native-command"
aria-label="Copy function analysis"
>
Copy
</button>
</div>
<pre><code id="native-command">npx -y rea-agents@latest function /absolute/path/to/program main \
--provider ghidra --json</code></pre>
</div>
<p>
Replace the target path and <code>main</code> with your own
function name or address. Each CLI invocation imports and analyzes
the binary, then closes its session. Your agent's MCP queries
reuse the imported binary while that session is open.
</p>
<p>
Read the pseudocode alongside instructions and references. When a
result leaves a call or value unresolved, use that location as the
next question.
</p>
</section>
</article>
</div>
</main>
<footer class="site-footer">
<div class="container footer-inner">
<p>REA · Reverse Engineer Anything</p>
<div class="footer-links">
<a href="https://github.com/morluto/rea">Source</a
><a href="https://github.com/morluto/rea/blob/main/LICENSE"
>MIT license</a
><a href="https://discord.gg/GkcryMnJDM">Community</a>
</div>
</div>
</footer>
<p class="sr-only" id="copy-status" role="status" aria-live="polite"></p>
</body>
</html>

View File

@ -24,7 +24,7 @@
<nav class="navigation" aria-label="Main navigation">
<a href="showcase/dx-ball/">DX-Ball</a>
<a class="nav-start" href="get-started/">Get started</a>
<a href="https://github.com/morluto/rea/tree/main/docs">Docs</a>
<a href="guides/">Guides</a>
<a href="https://github.com/morluto/rea">GitHub</a>
</nav>
</div>
@ -150,10 +150,7 @@
Inspect functions, strings, references and call relationships in
executables and libraries.
</p>
<a
href="https://github.com/morluto/rea/blob/main/docs/native-investigation.md"
>Native analysis guide</a
>
<a href="guides/native/">Native analysis guide</a>
</div>
<div>
<h3>JavaScript &amp; Electron</h3>
@ -161,10 +158,7 @@
Map modules, routes, IPC and native dependencies from an
application folder or ASAR archive.
</p>
<a
href="https://github.com/morluto/rea/blob/main/docs/javascript-application-workflows.md"
>Application workflows</a
>
<a href="guides/javascript/">Application workflows</a>
</div>
<div>
<h3>Runtime behavior</h3>
@ -172,10 +166,7 @@
Capture selected browser or process activity, then compare the
results across runs.
</p>
<a
href="https://github.com/morluto/rea/blob/main/docs/browser-observation.md"
>Browser observation guide</a
>
<a href="guides/browser/">Browser observation guide</a>
</div>
</div>
</div>

View File

@ -24,7 +24,7 @@
<nav class="navigation" aria-label="Main navigation">
<a href="./" aria-current="page">DX-Ball</a>
<a class="nav-start" href="../../get-started/">Get started</a>
<a href="https://github.com/morluto/rea/tree/main/docs">Docs</a>
<a href="../../guides/">Guides</a>
<a href="https://github.com/morluto/rea">GitHub</a>
</nav>
</div>
@ -125,6 +125,34 @@
sound code. The investigation needed to recover how that screen
coordinate becomes a left/right panning value.
</p>
<div class="agent-terminal">
<div class="terminal-header">
<span class="terminal-title"
><span class="terminal-dots" aria-hidden="true"
><i></i><i></i><i></i></span
>Your coding agent</span
><button
class="copy-button"
type="button"
data-copy="dxball-prompt"
aria-label="Copy example prompt"
>
Copy
</button>
</div>
<p class="terminal-prompt">
<span class="prompt-symbol" aria-hidden="true">›</span
><span id="dxball-prompt"
>Use REA to find how DX-Ball calculates sound panning. Explain
the calculation and show the code.</span
><span class="terminal-cursor" aria-hidden="true"></span>
</p>
</div>
<p class="caption">
An example prompt for your coding agent, with the local executable
available. The investigation below shows the REA queries and
evidence behind the answer.
</p>
<p>
The agent used REA to inspect the function, follow its brick-hit
caller and read the constants from the executable. Those results
@ -567,10 +595,11 @@
><span>Current source, progress and local setup.</span>
</li>
<li>
<a
href="https://github.com/N0zoM1z0/dx-ball/blob/a55dca27ec0a07018c1b2c95ae2be027f7d8c3c4/docs/REA.md"
>REA analysis workflow</a
><span>Pinned tools, MCP sessions and retained evidence.</span>
<a href="../../guides/native/">REA analysis workflow</a
><span
>Follow the calculation, then use the same approach with your
own binary.</span
>
</li>
<li>
<a