diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index dde66fcf4..e276a9f0d 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -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 diff --git a/.github/workflows/website-check.yml b/.github/workflows/website-check.yml new file mode 100644 index 000000000..42257f867 --- /dev/null +++ b/.github/workflows/website-check.yml @@ -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 diff --git a/.github/workflows/website-pages.yml b/.github/workflows/website-pages.yml index 3967a89e4..a6118bb6a 100644 --- a/.github/workflows/website-pages.yml +++ b/.github/workflows/website-pages.yml @@ -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 diff --git a/.oxlintrc.json b/.oxlintrc.json index 8ca5017b3..2da0945d6 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -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" diff --git a/scripts/verify-website.py b/scripts/verify-website.py new file mode 100644 index 000000000..60e8e0b0f --- /dev/null +++ b/scripts/verify-website.py @@ -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()) diff --git a/website/README.md b/website/README.md index edb950181..434cc2db2 100644 --- a/website/README.md +++ b/website/README.md @@ -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 . 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. diff --git a/website/evidence/guide-examples.md b/website/evidence/guide-examples.md new file mode 100644 index 000000000..cae290a8c --- /dev/null +++ b/website/evidence/guide-examples.md @@ -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` | diff --git a/website/public/assets/main.js b/website/public/assets/main.js index 45e6adc98..00efa23a3 100644 --- a/website/public/assets/main.js +++ b/website/public/assets/main.js @@ -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(); diff --git a/website/public/assets/styles.css b/website/public/assets/styles.css index fde289bf7..f27f68211 100644 --- a/website/public/assets/styles.css +++ b/website/public/assets/styles.css @@ -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; diff --git a/website/public/examples/notes-electron/csv.js b/website/public/examples/notes-electron/csv.js new file mode 100644 index 000000000..3ebfd4cc6 --- /dev/null +++ b/website/public/examples/notes-electron/csv.js @@ -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 }; diff --git a/website/public/examples/notes-electron/index.html b/website/public/examples/notes-electron/index.html new file mode 100644 index 000000000..fc4e3a62d --- /dev/null +++ b/website/public/examples/notes-electron/index.html @@ -0,0 +1,13 @@ + + + + + Notes — Electron analysis example + + + +

Notes

+ +

+ + diff --git a/website/public/examples/notes-electron/main.js b/website/public/examples/notes-electron/main.js new file mode 100644 index 000000000..6cfa662e7 --- /dev/null +++ b/website/public/examples/notes-electron/main.js @@ -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"); +}); diff --git a/website/public/examples/notes-electron/package.json b/website/public/examples/notes-electron/package.json new file mode 100644 index 000000000..3840d8f31 --- /dev/null +++ b/website/public/examples/notes-electron/package.json @@ -0,0 +1,6 @@ +{ + "name": "rea-notes-example", + "version": "1.0.0", + "private": true, + "main": "main.js" +} diff --git a/website/public/examples/notes-electron/preload.js b/website/public/examples/notes-electron/preload.js new file mode 100644 index 000000000..9c0aed503 --- /dev/null +++ b/website/public/examples/notes-electron/preload.js @@ -0,0 +1,5 @@ +const { contextBridge, ipcRenderer } = require("electron"); + +contextBridge.exposeInMainWorld("notes", { + exportCsv: () => ipcRenderer.invoke("notes:export"), +}); diff --git a/website/public/examples/notes-electron/renderer.js b/website/public/examples/notes-electron/renderer.js new file mode 100644 index 000000000..50feeb443 --- /dev/null +++ b/website/public/examples/notes-electron/renderer.js @@ -0,0 +1,4 @@ +document.querySelector("#export").addEventListener("click", async () => { + const destination = await window.notes.exportCsv(); + document.querySelector("#status").textContent = `Saved to ${destination}`; +}); diff --git a/website/public/examples/notes-web/export.js b/website/public/examples/notes-web/export.js new file mode 100644 index 000000000..8b1d4d0c5 --- /dev/null +++ b/website/public/examples/notes-web/export.js @@ -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; + } +}); diff --git a/website/public/examples/notes-web/index.html b/website/public/examples/notes-web/index.html new file mode 100644 index 000000000..00d2c80b8 --- /dev/null +++ b/website/public/examples/notes-web/index.html @@ -0,0 +1,35 @@ + + + + + + Notes — REA browser example + + + + + +
+ +

A small app to inspect

+

Notes

+

+ Two notes. One CSV export. Observe what happens when you click. +

+ + +

+ Ready to export. +

+

+ This example loads notes.json and creates a CSV in your + browser. +

+
+ + diff --git a/website/public/examples/notes-web/notes.json b/website/public/examples/notes-web/notes.json new file mode 100644 index 000000000..bb67dc2d3 --- /dev/null +++ b/website/public/examples/notes-web/notes.json @@ -0,0 +1,6 @@ +{ + "notes": [ + { "id": 1, "title": "First note" }, + { "id": 2, "title": "Follow the export" } + ] +} diff --git a/website/public/get-started/index.html b/website/public/get-started/index.html index 26ba6a21f..0541e21c4 100644 --- a/website/public/get-started/index.html +++ b/website/public/get-started/index.html @@ -24,7 +24,7 @@ @@ -39,10 +39,6 @@

You'll need Node.js 22 (22.19+), 24 (24.11+) or 26+, and npm. - Full installation guide

@@ -59,11 +55,9 @@

With your agent

- Run setup to connect REA to Claude Code, Codex, Cursor or another - supported client. + Run setup to connect REA to your coding agent. Client choices + include Claude Code, Codex, Cursor, Gemini CLI, Windsurf and other + supported MCP clients.

@@ -154,8 +148,7 @@
npm install --global rea-agents

- Continue with the JavaScript application guide

@@ -164,47 +157,111 @@

Working with native binaries

- 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.

Ghidra

- 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:

- Ghidra setup and supported hosts +
+ Connect your Ghidra installation +
+
+ Ghidra connection · macOS / Linux +
+
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
+
+

+ 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. +

+

Hopper

- 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. +

+

+ On macOS, complete Hopper's first-run choice of demo mode or + license activation before an unattended analysis.

- Hopper installation

IDA

- 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.

- IDA connection guide +
+ Connect an existing IDA MCP registration +

+ Keep the upstream server's working command, + args and optional env in a JSON + file. An existing mcpServers object with an + ida-pro-mcp entry is also accepted. Point REA + at that file before setup: +

+
+
+ IDA connection · Windows PowerShell +
+
$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
+
+

+ 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 "mode": "headless"; real + verification covers an IDA 9.3 Windows x64 supervisor. + Doctor checks the registration without starting IDA. +

+

+ Give REA the original executable path, with + --provider ida in a CLI query. For an agent, + include REA_IDA_MCP_CONFIG in the REA + registration's environment and select IDA when opening the + binary. +

+
@@ -228,8 +285,13 @@

Replace main 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. +

+

+ Follow a native analysis example + to see how the function result leads to a recovered calculation.

@@ -273,12 +335,10 @@

The report gives the failed check and a suggested next step.

@@ -150,10 +150,7 @@ Inspect functions, strings, references and call relationships in executables and libraries.

- Native analysis guide + Native analysis guide

JavaScript & Electron

@@ -161,10 +158,7 @@ Map modules, routes, IPC and native dependencies from an application folder or ASAR archive.

- Application workflows + Application workflows

Runtime behavior

@@ -172,10 +166,7 @@ Capture selected browser or process activity, then compare the results across runs.

- Browser observation guide + Browser observation guide
diff --git a/website/public/showcase/dx-ball/index.html b/website/public/showcase/dx-ball/index.html index 6d4bfdf0f..f58308465 100644 --- a/website/public/showcase/dx-ball/index.html +++ b/website/public/showcase/dx-ball/index.html @@ -24,7 +24,7 @@ @@ -125,6 +125,34 @@ sound code. The investigation needed to recover how that screen coordinate becomes a left/right panning value.

+
+
+ Your coding agent +
+

+ Use REA to find how DX-Ball calculates sound panning. Explain + the calculation and show the code. +

+
+

+ An example prompt for your coding agent, with the local executable + available. The investigation below shows the REA queries and + evidence behind the answer. +

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 @@ >Current source, progress and local setup.

  • - REA analysis workflowPinned tools, MCP sessions and retained evidence. + REA analysis workflowFollow the calculation, then use the same approach with your + own binary.