Merge pull request #1296 from morluto/feat/website-seo-metadata
feat(website): add search metadata and automatic sitemap
This commit is contained in:
commit
99f257bbf0
|
|
@ -5,8 +5,7 @@ on:
|
|||
branches: [main, "release/*"]
|
||||
paths:
|
||||
- "website/**"
|
||||
- "scripts/verify-website.py"
|
||||
- "scripts/prepare-website.py"
|
||||
- "scripts/*website*.py"
|
||||
- ".github/workflows/**"
|
||||
workflow_dispatch:
|
||||
|
||||
|
|
@ -21,11 +20,24 @@ jobs:
|
|||
check:
|
||||
name: Website checks
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Check out the repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Prepare the example download
|
||||
- name: Set up Python for website assets
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
cache: pip
|
||||
cache-dependency-path: website/requirements.txt
|
||||
- name: Install sharing-image dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install --no-install-recommends -y libcairo2 fonts-dejavu-core
|
||||
python3 -m pip install -r website/requirements.txt
|
||||
- name: Prepare website assets
|
||||
run: python3 scripts/prepare-website.py
|
||||
- name: Check website links and publishing configuration
|
||||
- name: Check website links, metadata and publishing configuration
|
||||
run: python3 scripts/verify-website.py
|
||||
- name: Check sitemap discovery and metadata regressions
|
||||
run: python3 scripts/test-website.py
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ jobs:
|
|||
prepare:
|
||||
if: github.ref == 'refs/heads/main'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
pages: read
|
||||
|
|
@ -48,9 +48,20 @@ jobs:
|
|||
fi
|
||||
- name: Check out the repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Prepare the example download
|
||||
- name: Set up Python for website assets
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
cache: pip
|
||||
cache-dependency-path: website/requirements.txt
|
||||
- name: Install sharing-image dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install --no-install-recommends -y libcairo2 fonts-dejavu-core
|
||||
python3 -m pip install -r website/requirements.txt
|
||||
- name: Prepare website assets
|
||||
run: python3 scripts/prepare-website.py
|
||||
- name: Check website links and publishing configuration
|
||||
- name: Check website links, metadata and publishing configuration
|
||||
run: python3 scripts/verify-website.py
|
||||
- name: Record the published commit
|
||||
run: |
|
||||
|
|
|
|||
|
|
@ -50,6 +50,9 @@ Thumbs.db
|
|||
# Generated website download
|
||||
/website/public/examples/notes-example.zip
|
||||
/website/public/deployment.json
|
||||
/website/public/sitemap.xml
|
||||
/website/public/assets/social-card.png
|
||||
/website/.venv/
|
||||
|
||||
# Local Cloudflare development state
|
||||
/website/.wrangler/
|
||||
|
|
|
|||
|
|
@ -1,10 +1,16 @@
|
|||
"""Package the public Notes example for the static website."""
|
||||
"""Prepare downloads, the sitemap and the sharing image for the static website."""
|
||||
|
||||
from html.parser import HTMLParser
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
import re
|
||||
import sys
|
||||
from urllib.parse import quote
|
||||
import xml.etree.ElementTree as ET
|
||||
from zipfile import ZIP_DEFLATED, ZipFile, ZipInfo
|
||||
|
||||
|
||||
SITE_ORIGIN = "https://rea.tools"
|
||||
EXAMPLE_FILES = (
|
||||
"package.json",
|
||||
"main.js",
|
||||
|
|
@ -15,6 +21,70 @@ EXAMPLE_FILES = (
|
|||
)
|
||||
|
||||
|
||||
class IndexPolicy(HTMLParser):
|
||||
"""Read indexing directives from the authored HTML head."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.in_head = False
|
||||
self.noindex = False
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
if tag == "head":
|
||||
self.in_head = True
|
||||
if tag == "meta" and self.in_head:
|
||||
attributes = dict(attrs)
|
||||
if (attributes.get("name") or "").lower() in ("robots", "googlebot"):
|
||||
directives = re.split(
|
||||
r"[\s,]+", (attributes.get("content") or "").lower()
|
||||
)
|
||||
self.noindex |= bool(set(directives) & {"noindex", "none"})
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
if tag == "head":
|
||||
self.in_head = False
|
||||
|
||||
|
||||
def prepare_sitemap(site):
|
||||
"""Discover HTML routes automatically, excluding pages marked noindex."""
|
||||
namespace = "http://www.sitemaps.org/schemas/sitemap/0.9"
|
||||
ET.register_namespace("", namespace)
|
||||
sitemap = ET.Element(f"{{{namespace}}}urlset")
|
||||
for path in sorted(site.rglob("*.html")):
|
||||
policy = IndexPolicy()
|
||||
policy.feed(path.read_text(encoding="utf-8"))
|
||||
if policy.noindex:
|
||||
continue
|
||||
route = path.relative_to(site).as_posix()
|
||||
if path.name == "index.html":
|
||||
route = route.removesuffix("index.html")
|
||||
entry = ET.SubElement(sitemap, f"{{{namespace}}}url")
|
||||
ET.SubElement(
|
||||
entry, f"{{{namespace}}}loc"
|
||||
).text = f"{SITE_ORIGIN}/{quote(route, safe='/')}"
|
||||
ET.indent(sitemap, space=" ")
|
||||
ET.ElementTree(sitemap).write(
|
||||
site / "sitemap.xml", encoding="utf-8", xml_declaration=True
|
||||
)
|
||||
print(f"Prepared sitemap.xml ({len(sitemap)} indexable pages).")
|
||||
|
||||
|
||||
def prepare_sharing_image(site):
|
||||
"""Rasterize the maintained SVG for clients that need a PNG preview."""
|
||||
try:
|
||||
import cairosvg
|
||||
except (ImportError, OSError) as error:
|
||||
sys.exit(
|
||||
f"Cannot prepare sharing image: {error}. Install website/requirements.txt and native Cairo; see website/README.md."
|
||||
)
|
||||
source = site / "assets/social-card.svg"
|
||||
destination = site / "assets/social-card.png"
|
||||
cairosvg.svg2png(
|
||||
url=str(source), write_to=str(destination), output_width=1200, output_height=630
|
||||
)
|
||||
print("Prepared assets/social-card.png (1200 × 630).")
|
||||
|
||||
|
||||
def prepare_notes_example(site):
|
||||
"""Create a ZIP with public source only and reproducible metadata."""
|
||||
source = site / "examples/notes-electron"
|
||||
|
|
@ -37,4 +107,7 @@ def prepare_notes_example(site):
|
|||
|
||||
if __name__ == "__main__":
|
||||
root = Path(__file__).resolve().parent.parent
|
||||
prepare_notes_example(root / "website/public")
|
||||
site = root / "website/public"
|
||||
prepare_sharing_image(site)
|
||||
prepare_sitemap(site)
|
||||
prepare_notes_example(site)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,130 @@
|
|||
"""Exercise automatic route discovery and mistakes that break search metadata."""
|
||||
|
||||
import contextlib
|
||||
import importlib.util
|
||||
import io
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import tempfile
|
||||
import unittest
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
def load_script(name):
|
||||
"""Load a repository script without executing its command-line entrypoint."""
|
||||
spec = importlib.util.spec_from_file_location(name, ROOT / "scripts" / f"{name}.py")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
PREPARE = load_script("prepare-website")
|
||||
VERIFY = load_script("verify-website")
|
||||
|
||||
|
||||
class WebsiteSearchChecks(unittest.TestCase):
|
||||
"""Use authored pages and temporary fixtures to check the publishing boundary."""
|
||||
|
||||
def test_sitemap_tracks_added_moved_removed_and_nonindexable_pages(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
site = Path(directory)
|
||||
|
||||
def page(name, head="", body=""):
|
||||
path = site / name
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(f"<html><head>{head}</head><body>{body}</body></html>")
|
||||
return path
|
||||
|
||||
page("index.html")
|
||||
nested = page("guides/new/index.html")
|
||||
page(
|
||||
"examples/fixture/index.html",
|
||||
'<meta name="robots" content="NOINDEX, follow">',
|
||||
)
|
||||
page("draft.html", '<meta name="googlebot" content="none">')
|
||||
page("code & notes.html")
|
||||
page("visible/index.html", body='<meta name="robots" content="noindex">')
|
||||
(site / "asset.json").write_text("{}")
|
||||
|
||||
def urls():
|
||||
with contextlib.redirect_stdout(io.StringIO()):
|
||||
PREPARE.prepare_sitemap(site)
|
||||
sitemap = ET.parse(site / "sitemap.xml")
|
||||
return {node.text for node in sitemap.findall(".//{*}loc")}
|
||||
|
||||
self.assertEqual(
|
||||
urls(),
|
||||
{
|
||||
"https://rea.tools/",
|
||||
"https://rea.tools/guides/new/",
|
||||
"https://rea.tools/code%20%26%20notes.html",
|
||||
"https://rea.tools/visible/",
|
||||
},
|
||||
)
|
||||
nested.rename(site / "moved.html")
|
||||
self.assertIn("https://rea.tools/moved.html", urls())
|
||||
self.assertNotIn("https://rea.tools/guides/new/", urls())
|
||||
(site / "moved.html").unlink()
|
||||
self.assertNotIn("https://rea.tools/moved.html", urls())
|
||||
|
||||
def test_verifier_rejects_wrong_canonicals_stale_sitemaps_and_missing_previews(
|
||||
self,
|
||||
):
|
||||
cases = (
|
||||
(
|
||||
"index.html",
|
||||
'rel="canonical" href="https://rea.tools/"',
|
||||
'rel="canonical" href="https://morluto.github.io/rea/"',
|
||||
"head canonical",
|
||||
),
|
||||
(
|
||||
"showcase/aegis/index.html",
|
||||
'rel="canonical" href="https://rea.tools/showcase/aegis/"',
|
||||
'rel="canonical" href="https://rea.tools/"',
|
||||
"head canonical",
|
||||
),
|
||||
(
|
||||
"examples/notes-electron/index.html",
|
||||
'content="noindex"',
|
||||
'content="index"',
|
||||
"must have a head noindex",
|
||||
),
|
||||
(
|
||||
"sitemap.xml",
|
||||
"https://rea.tools/faq/",
|
||||
"https://rea.tools/deleted/",
|
||||
"each indexable page once",
|
||||
),
|
||||
(
|
||||
"faq/index.html",
|
||||
'name="twitter:card" content="summary_large_image"',
|
||||
'name="twitter:card" content="summary"',
|
||||
"twitter:card",
|
||||
),
|
||||
)
|
||||
for name, old, new, message in cases:
|
||||
with self.subTest(name=name), tempfile.TemporaryDirectory() as directory:
|
||||
site = Path(directory) / "public"
|
||||
shutil.copytree(ROOT / "website/public", site)
|
||||
path = site / name
|
||||
source = path.read_text()
|
||||
self.assertIn(old, source)
|
||||
path.write_text(source.replace(old, new, 1))
|
||||
self.assertTrue(
|
||||
any(message in error for error in VERIFY.check_site(site)[3])
|
||||
)
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
site = Path(directory) / "public"
|
||||
shutil.copytree(ROOT / "website/public", site)
|
||||
(site / "assets/social-card.png").unlink()
|
||||
self.assertIn(
|
||||
"Social preview PNG is missing; run scripts/prepare-website.py",
|
||||
VERIFY.check_site(site)[3],
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
@ -1,14 +1,19 @@
|
|||
"""Check local website references and the sole GitHub Pages publisher."""
|
||||
"""Check website references, search metadata and the sole production publisher."""
|
||||
|
||||
from html.parser import HTMLParser
|
||||
from pathlib import Path
|
||||
import re
|
||||
import struct
|
||||
import sys
|
||||
from urllib.parse import unquote, urlsplit
|
||||
from urllib.parse import quote, unquote, urlsplit
|
||||
import xml.etree.ElementTree as ET
|
||||
from zipfile import BadZipFile, ZipFile
|
||||
|
||||
|
||||
SITE_ORIGIN = "https://rea.tools"
|
||||
NOTES_FIXTURES = {"examples/notes-electron/index.html", "examples/notes-web/index.html"}
|
||||
|
||||
|
||||
class HtmlReferences(HTMLParser):
|
||||
"""Collect document IDs and references without fetching external URLs."""
|
||||
|
||||
|
|
@ -17,9 +22,42 @@ class HtmlReferences(HTMLParser):
|
|||
self.ids = set()
|
||||
self.references = []
|
||||
self.duplicate_ids = []
|
||||
self.in_head = False
|
||||
self.in_title = False
|
||||
self.titles = []
|
||||
self.meta = {}
|
||||
self.properties = {}
|
||||
self.canonicals = []
|
||||
self.lang = None
|
||||
self.h1_count = 0
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
attributes = dict(attrs)
|
||||
if tag == "html":
|
||||
self.lang = attributes.get("lang")
|
||||
if tag == "h1":
|
||||
self.h1_count += 1
|
||||
if tag == "head":
|
||||
self.in_head = True
|
||||
if self.in_head:
|
||||
if tag == "title":
|
||||
self.titles.append("")
|
||||
self.in_title = True
|
||||
if (
|
||||
tag == "link"
|
||||
and "canonical" in (attributes.get("rel") or "").lower().split()
|
||||
):
|
||||
self.canonicals.append(attributes.get("href") or "")
|
||||
if tag == "meta":
|
||||
for attribute, collection in (
|
||||
("name", self.meta),
|
||||
("property", self.properties),
|
||||
):
|
||||
key = attributes.get(attribute)
|
||||
if key:
|
||||
collection.setdefault(key.lower(), []).append(
|
||||
attributes.get("content") or ""
|
||||
)
|
||||
identifier = attributes.get("id")
|
||||
if identifier is not None:
|
||||
if identifier in self.ids:
|
||||
|
|
@ -30,6 +68,154 @@ class HtmlReferences(HTMLParser):
|
|||
if reference is not None:
|
||||
self.references.append(reference)
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
if tag == "head":
|
||||
self.in_head = False
|
||||
if tag == "title":
|
||||
self.in_title = False
|
||||
|
||||
def handle_data(self, data):
|
||||
if self.in_title:
|
||||
self.titles[-1] += data
|
||||
|
||||
def noindex(self):
|
||||
"""Recognize directives that exclude this page from Google's index."""
|
||||
directives = ",".join(
|
||||
self.meta.get("robots", []) + self.meta.get("googlebot", [])
|
||||
).lower()
|
||||
return bool(set(re.split(r"[\s,]+", directives)) & {"noindex", "none"})
|
||||
|
||||
|
||||
def check_search_metadata(site, pages):
|
||||
"""Verify route-specific canonicals, sharing assets and the generated sitemap."""
|
||||
errors = []
|
||||
indexable = set()
|
||||
titles = set()
|
||||
for path, page in pages.items():
|
||||
label = path.relative_to(site).as_posix()
|
||||
title = page.titles[0].strip() if len(page.titles) == 1 else ""
|
||||
if not title:
|
||||
errors.append(f"{label}: needs exactly one nonempty head title")
|
||||
if page.lang != "en" or page.h1_count != 1:
|
||||
errors.append(f"{label}: needs lang=en and exactly one h1")
|
||||
if label in NOTES_FIXTURES and not page.noindex():
|
||||
errors.append(
|
||||
f"{label}: example application must have a head noindex directive"
|
||||
)
|
||||
if page.noindex():
|
||||
continue
|
||||
route = (
|
||||
path.relative_to(site).parent.as_posix()
|
||||
if path.name == "index.html"
|
||||
else label
|
||||
)
|
||||
route = "" if route == "." else route
|
||||
url = f"{SITE_ORIGIN}/{quote(route, safe='/')}" + (
|
||||
"/" if route and path.name == "index.html" else ""
|
||||
)
|
||||
indexable.add(url)
|
||||
if page.canonicals != [url]:
|
||||
errors.append(f"{label}: needs exactly one head canonical for {url}")
|
||||
if title in titles:
|
||||
errors.append(f"{label}: duplicate search title {title!r}")
|
||||
titles.add(title)
|
||||
descriptions = page.meta.get("description", [])
|
||||
description = descriptions[0].strip() if len(descriptions) == 1 else ""
|
||||
if not description:
|
||||
errors.append(f"{label}: needs exactly one nonempty head description")
|
||||
expected_og = {
|
||||
"og:type": "website",
|
||||
"og:site_name": "REA",
|
||||
"og:title": title,
|
||||
"og:description": description,
|
||||
"og:url": url,
|
||||
"og:image": f"{SITE_ORIGIN}/assets/social-card.png",
|
||||
"og:image:type": "image/png",
|
||||
"og:image:width": "1200",
|
||||
"og:image:height": "630",
|
||||
}
|
||||
expected_twitter = {
|
||||
"twitter:card": "summary_large_image",
|
||||
"twitter:title": title,
|
||||
"twitter:description": description,
|
||||
"twitter:image": expected_og["og:image"],
|
||||
}
|
||||
for collection, expected in (
|
||||
(page.properties, expected_og),
|
||||
(page.meta, expected_twitter),
|
||||
):
|
||||
for key, value in expected.items():
|
||||
if collection.get(key) != [value]:
|
||||
errors.append(
|
||||
f"{label}: {key} must appear once and match {value!r}"
|
||||
)
|
||||
for collection, key in (
|
||||
(page.properties, "og:image:alt"),
|
||||
(page.meta, "twitter:image:alt"),
|
||||
):
|
||||
values = collection.get(key, [])
|
||||
if len(values) != 1 or not values[0].strip():
|
||||
errors.append(f"{label}: needs exactly one nonempty {key}")
|
||||
|
||||
sitemap_path = site / "sitemap.xml"
|
||||
try:
|
||||
sitemap = ET.parse(sitemap_path).getroot()
|
||||
namespace = "{http://www.sitemaps.org/schemas/sitemap/0.9}"
|
||||
urls = [entry.findtext(f"{namespace}loc") for entry in sitemap]
|
||||
if sitemap.tag != f"{namespace}urlset" or any(
|
||||
entry.tag != f"{namespace}url" for entry in sitemap
|
||||
):
|
||||
errors.append("sitemap.xml: needs a sitemap-protocol urlset")
|
||||
if len(urls) != len(set(urls)) or set(urls) != indexable:
|
||||
errors.append(
|
||||
"sitemap.xml: must contain each indexable page once, using its rea.tools URL"
|
||||
)
|
||||
if any(
|
||||
len(entry) != 1 or entry[0].tag != f"{namespace}loc" for entry in sitemap
|
||||
):
|
||||
errors.append(
|
||||
"sitemap.xml: keep entries to loc; do not invent lastmod or add priority/changefreq"
|
||||
)
|
||||
except (OSError, ET.ParseError) as error:
|
||||
errors.append(
|
||||
f"sitemap.xml: cannot read sitemap ({error}); run scripts/prepare-website.py"
|
||||
)
|
||||
|
||||
robots_path = site / "robots.txt"
|
||||
if not robots_path.is_file():
|
||||
errors.append("robots.txt: authored crawl rules are missing")
|
||||
else:
|
||||
directives = []
|
||||
for line in robots_path.read_text(encoding="utf-8").splitlines():
|
||||
value = line.split("#", 1)[0].strip()
|
||||
if value:
|
||||
key, separator, content = value.partition(":")
|
||||
directives.append(
|
||||
(key.lower(), content.strip()) if separator else (value, "")
|
||||
)
|
||||
expected = [
|
||||
("user-agent", "*"),
|
||||
("allow", "/"),
|
||||
("sitemap", f"{SITE_ORIGIN}/sitemap.xml"),
|
||||
]
|
||||
if directives != expected:
|
||||
errors.append(
|
||||
"robots.txt: expected crawl access and one absolute rea.tools sitemap directive"
|
||||
)
|
||||
|
||||
image = site / "assets/social-card.png"
|
||||
if not image.is_file():
|
||||
errors.append("Social preview PNG is missing; run scripts/prepare-website.py")
|
||||
else:
|
||||
header = image.read_bytes()[:24]
|
||||
if (
|
||||
len(header) != 24
|
||||
or header[:16] != b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR"
|
||||
or struct.unpack(">II", header[16:24]) != (1200, 630)
|
||||
):
|
||||
errors.append("Social preview must be a 1200 × 630 PNG")
|
||||
return len(indexable), errors
|
||||
|
||||
|
||||
def check_site(site):
|
||||
"""Check assets, HTML fragment destinations and SVG syntax."""
|
||||
|
|
@ -63,7 +249,11 @@ def check_site(site):
|
|||
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:
|
||||
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")):
|
||||
|
|
@ -71,18 +261,23 @@ def check_site(site):
|
|||
ET.parse(path)
|
||||
except ET.ParseError as error:
|
||||
errors.append(f"{path.relative_to(site)}: invalid SVG XML: {error}")
|
||||
return len(pages), references, errors
|
||||
indexable, metadata_errors = check_search_metadata(site, pages)
|
||||
errors.extend(metadata_errors)
|
||||
return len(pages), references, indexable, 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)
|
||||
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 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"
|
||||
|
|
@ -95,7 +290,14 @@ def check_example_archive(site):
|
|||
source = site / "examples/notes-electron"
|
||||
expected = {
|
||||
f"notes-example/{name}": source / name
|
||||
for name in ("package.json", "main.js", "preload.js", "renderer.js", "csv.js", "index.html")
|
||||
for name in (
|
||||
"package.json",
|
||||
"main.js",
|
||||
"preload.js",
|
||||
"renderer.js",
|
||||
"csv.js",
|
||||
"index.html",
|
||||
)
|
||||
}
|
||||
path = site / "examples/notes-example.zip"
|
||||
if not path.is_file():
|
||||
|
|
@ -103,7 +305,9 @@ def check_example_archive(site):
|
|||
try:
|
||||
with ZipFile(path) as archive:
|
||||
if sorted(archive.namelist()) != sorted(expected):
|
||||
return ["Notes example ZIP must contain exactly the six files in notes-example/."]
|
||||
return [
|
||||
"Notes example ZIP must contain exactly the six files in notes-example/."
|
||||
]
|
||||
return [
|
||||
f"Notes example ZIP has stale source: {name}"
|
||||
for name, original in expected.items()
|
||||
|
|
@ -116,13 +320,18 @@ def check_example_archive(site):
|
|||
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")
|
||||
pages, references, indexable, errors = check_site(root / "website/public")
|
||||
errors.extend(check_example_archive(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, SVG XML and the example ZIP.")
|
||||
print(
|
||||
f"Verified {pages} HTML pages, {references} local references, SVG XML and the example ZIP."
|
||||
)
|
||||
print(
|
||||
f"Verified search/sharing metadata, robots.txt and the sitemap for {indexable} indexable pages."
|
||||
)
|
||||
print("website-pages.yml is the sole GitHub Pages publisher.")
|
||||
return 0
|
||||
|
||||
|
|
|
|||
|
|
@ -16,9 +16,15 @@ for asset notes and the source of case-study claims.
|
|||
target identity and verification scope.
|
||||
- Keep personal paths, account data, credentials and raw captures outside the
|
||||
public assets. Use generic inputs and label shortened display paths.
|
||||
- Follow the same clear writing standard in search titles and descriptions.
|
||||
Give each content page its own absolute `https://rea.tools/` canonical and
|
||||
matching sharing metadata; keep navigation relative. The sitemap is generated
|
||||
from HTML automatically. Mark example applications `noindex` in the head and
|
||||
keep them crawlable. Maintain the SVG sharing source, not the generated PNG.
|
||||
- Check both local root and `/rea/` paths, mobile layouts, expanded details,
|
||||
copying and downloads. Run `scripts/prepare-website.py` followed by
|
||||
`scripts/verify-website.py`.
|
||||
`scripts/verify-website.py` and `scripts/test-website.py`, using the website
|
||||
Python environment described in the README.
|
||||
- Keep `website-pages.yml` as the sole, manual production publisher for
|
||||
Cloudflare and Pages. Default to `both`: both hosts receive the same verified
|
||||
artifact and pass the published-commit check. Use `pages` only when explicitly
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@
|
|||
An English static website with explanatory figures, worked guides and Aegis, DX-Ball, Notion, TH04 and CTF investigations.
|
||||
The public files are in `website/public/`. The site uses HTML, CSS and small
|
||||
scripts for copying code, following code comparisons and playing the dinosaur
|
||||
speed reconstruction. Python
|
||||
packages the downloadable example; there is no frontend bundler or npm dependency.
|
||||
speed reconstruction. Python prepares the example ZIP, sitemap and sharing
|
||||
image; there is no frontend bundler or npm dependency.
|
||||
|
||||
[style-guide.md](style-guide.md) explains the writing, page structure, figures,
|
||||
visual system and review process. Read it before adding or revising a page.
|
||||
|
|
@ -18,11 +18,23 @@ visual system and review process. Read it before adding or revising a page.
|
|||
From the repository root:
|
||||
|
||||
```sh
|
||||
python3 -m venv website/.venv
|
||||
source website/.venv/bin/activate
|
||||
python3 -m pip install -r website/requirements.txt
|
||||
python3 scripts/prepare-website.py
|
||||
python3 -m http.server 4173 --bind 127.0.0.1 --directory website/public
|
||||
```
|
||||
|
||||
Open <http://127.0.0.1:4173/>. Refresh the browser after editing a file.
|
||||
Keep the virtual environment active when preparing assets. CairoSVG needs the
|
||||
native Cairo library; the sharing card uses DejaVu Sans. On Debian or Ubuntu,
|
||||
install them with `sudo apt-get install libcairo2 fonts-dejavu-core`. For other
|
||||
systems, see [CairoSVG's installation instructions](https://cairosvg.org/documentation/#installation).
|
||||
CI installs both packages explicitly before rendering the card.
|
||||
|
||||
After adding, moving or removing a page, or changing its indexing policy, rerun
|
||||
`python3 scripts/prepare-website.py`. It regenerates the sitemap from the current
|
||||
HTML files. No URL list needs to be maintained.
|
||||
|
||||
## Pages
|
||||
|
||||
|
|
@ -44,6 +56,69 @@ Open <http://127.0.0.1:4173/>. Refresh the browser after editing a file.
|
|||
Navigation and assets use relative paths, so the same files work at the local
|
||||
root and a GitHub Pages project path such as `/rea/`.
|
||||
|
||||
## Search and sharing metadata
|
||||
|
||||
`https://rea.tools/` is the preferred public origin. Each content page has one
|
||||
absolute canonical URL for its own route, including its trailing slash. The
|
||||
same HTML on GitHub Pages points to the corresponding rea.tools page. Ordinary
|
||||
navigation and asset references remain relative so both hosts and local previews
|
||||
keep working.
|
||||
|
||||
Maintain a descriptive `<title>` and a short meta description in each page's
|
||||
`<head>`. The title names the page's subject; visible headings keep the wording
|
||||
that best explains it to a reader. Open Graph and Twitter titles/descriptions
|
||||
match those fields. `og:url` matches the canonical, and sharing images use
|
||||
absolute rea.tools URLs. Search results and link previews should follow the same
|
||||
clear, concise writing standard as the page.
|
||||
|
||||
`scripts/prepare-website.py` scans `public/**/*.html` and generates
|
||||
`public/sitemap.xml` with absolute rea.tools URLs. An `index.html` maps to its
|
||||
directory route; other HTML filenames retain their extension. Pages with
|
||||
`noindex` or `none` in a head `robots` or `googlebot` meta directive are excluded.
|
||||
The two Notes example applications use `noindex`; their guides and the dinosaur
|
||||
lab remain indexable. Downloads, scripts and other non-HTML files are not listed.
|
||||
The sitemap has no guessed modification dates, priority or change frequency.
|
||||
It is ignored by Git and regenerated before checks and publication.
|
||||
|
||||
`public/robots.txt` allows crawling and names the canonical sitemap. Keep example
|
||||
applications crawlable so search engines can read their `noindex` directives.
|
||||
Cloudflare may prepend managed rules or comments to the served file; inspect the
|
||||
live response after publication. The mirrored `/rea/robots.txt` is not the
|
||||
hostname-root robots file for `morluto.github.io`; canonical HTML tags establish
|
||||
the mirror's preference.
|
||||
|
||||
`public/assets/social-card.svg` is the maintained 1200 × 630 preview source.
|
||||
CairoSVG generates `social-card.png` for sharing clients. Commit the SVG, not the
|
||||
PNG. Both hosts receive the generated image in the same publishing artifact.
|
||||
The preview uses the site's typography, palette and direct headline.
|
||||
|
||||
Verification checks route-specific canonicals, unique titles, descriptions,
|
||||
sharing fields, image dimensions, indexing policy and complete sitemap coverage.
|
||||
`scripts/test-website.py` exercises automatic route changes and rejects incorrect
|
||||
canonicals, lost fixture exclusions, stale sitemap entries and missing previews.
|
||||
Run it after preparing assets:
|
||||
|
||||
```sh
|
||||
python3 scripts/verify-website.py
|
||||
python3 scripts/test-website.py
|
||||
```
|
||||
|
||||
### After publishing an SEO change
|
||||
|
||||
1. Check the served `robots.txt`, `sitemap.xml`, HTML head and preview PNG at
|
||||
rea.tools. Confirm that both hosts contain the same page-specific canonicals.
|
||||
2. In the domain's Google Search Console property, submit
|
||||
`https://rea.tools/sitemap.xml`.
|
||||
3. Inspect the homepage, a guide and a case study. Check crawl access, indexing
|
||||
and Google's selected canonical; inspect a GitHub Pages URL as well if that
|
||||
property is available. Track indexing and search performance there afterward.
|
||||
|
||||
These Search Console checks require the property owner's access. Local checks
|
||||
verify the website inputs; Google's indexing reports show how they were used.
|
||||
See Google's [canonical guidance](https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls)
|
||||
and [sitemap guidance](https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap),
|
||||
and Cloudflare's [managed robots behavior](https://developers.cloudflare.com/bots/additional-configurations/managed-robots-txt/).
|
||||
|
||||
## Content
|
||||
|
||||
Keep the copy direct and specific. Explain the task and the result before listing
|
||||
|
|
@ -293,16 +368,18 @@ a release. Cloudflare deploys first; GitHub Pages deploys after that step succee
|
|||
|
||||
Preparation adds `deployment.json` with the workflow's commit SHA. Cloudflare
|
||||
receives the extracted Pages artifact through Wrangler's explicit assets path;
|
||||
the local custom-build output is not the uploaded directory. The workflow then
|
||||
the production environment disables Wrangler's preview custom build, so deployment
|
||||
uses the prepared artifact directly. The workflow then
|
||||
checks the selected public version markers and reports success only when they
|
||||
match that commit. A failed publication remains failed; fix its cause and rerun the
|
||||
workflow to complete both deployments. The two hosts can update at different
|
||||
times while a run is in progress.
|
||||
|
||||
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:
|
||||
Website checks run only for pull requests that change `website/`, the website
|
||||
scripts or workflow definitions. They check local links, HTML fragments, SVG XML,
|
||||
search/sharing metadata and the single Pages publisher without installing npm
|
||||
dependencies. The same asset preparation and verification run before each manual
|
||||
deployment. With the preview environment active, run them locally:
|
||||
|
||||
```sh
|
||||
python3 scripts/prepare-website.py
|
||||
|
|
|
|||
|
|
@ -33,8 +33,8 @@ when the example files change.
|
|||
The guide's `examples/notes-example.zip` is generated from exactly these six
|
||||
source files, under the `notes-example/` folder. Packaging changes no file
|
||||
contents. Website verification compares every ZIP entry to its source file and
|
||||
rejects extra entries. Existing source digests and static findings remain valid
|
||||
for the unzipped example.
|
||||
rejects extra entries. The recorded digests below identify the original analysis
|
||||
checkpoint; subsequent metadata changes and checks are recorded separately.
|
||||
|
||||
## First-investigation formatter check
|
||||
|
||||
|
|
@ -82,6 +82,23 @@ The guide's follow-up CLI commands were also exercised against the real page:
|
|||
source. Discovery Evidence: `ev_31abb33e7707d47625ac909ab9256c705d03a31ee6bcd643fa96730e72469162`. Script capture
|
||||
Evidence: `ev_aeb777df405c09dc1bbdd4c185a5af2452abaa3cb493683c296c5afa97efbc11`.
|
||||
|
||||
## SEO metadata check — 9 October 2026
|
||||
|
||||
Both Notes HTML heads gained descriptions and `noindex` directives. The Electron
|
||||
fixture was analyzed again with `rea-agents` 4.1.0 and Node.js 24.18.0 on Linux
|
||||
x64. REA still reports four parsed JavaScript files, zero parse failures, one
|
||||
exposed API member, one literal IPC channel, one renderer transmission, one main
|
||||
handler and one paired transmission, with zero ambiguous transmissions.
|
||||
|
||||
- Evidence ID: `ev_3958d870ecd27c202e5decc97161f2ea19db8cc88f9f39ed0449bf1581df2f2a`.
|
||||
- Root artifact SHA-256: `0ee325d4eaf7f94b62b2962044995fe1efe9f1d31411eb035118b19b1f56f7bb`.
|
||||
- Current Electron HTML SHA-256: `457b22543aa956fe2cd005fc6bda42d2798eb05040b75c4cb4957e3b66d0ea09`.
|
||||
- Current browser HTML SHA-256: `3b24b3307ded48055ce5a48f907544195bfde3285f82df919c54a6415ac11070`.
|
||||
|
||||
The JavaScript and data files retain their original digests. The generated ZIP
|
||||
contains the updated HTML and the same JavaScript. Local browser checks exercise
|
||||
the Notes CSV download at both root and `/rea/` paths.
|
||||
|
||||
Full local outputs and the browser verification harness are under
|
||||
`/tmp/rea-website-guides/` in the development environment, not website assets.
|
||||
|
||||
|
|
|
|||
|
|
@ -43,6 +43,15 @@ branches when changing these figures. Match progress claims to the pinned
|
|||
reconstruction checkpoint. Validate the SVG as XML and inspect its browser
|
||||
rendering at desktop and mobile widths.
|
||||
|
||||
## Sharing preview
|
||||
|
||||
`public/assets/social-card.svg` is the 1200 × 630 preview source. It uses the
|
||||
homepage's direct headline, the site's white/charcoal/slate/blue palette, and a
|
||||
short agent → REA → evidence flow. CairoSVG renders the ignored PNG during asset
|
||||
preparation. Check the rendered image for readable text and spacing before
|
||||
publishing. The build uses DejaVu Sans for consistent typography; only the SVG
|
||||
source is committed. Metadata and build instructions are in the website README.
|
||||
|
||||
## Beginner homepage and dinosaur reconstruction
|
||||
|
||||
The manual/agent comparison and Calculator percentage controls are semantic
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630" viewBox="0 0 1200 630" role="img" aria-labelledby="title desc">
|
||||
<title id="title">REA — Find out how software works.</title>
|
||||
<desc id="desc">Reverse engineering with your coding agent. REA connects your agent to analysis tools and returns evidence.</desc>
|
||||
<rect width="1200" height="630" fill="#ffffff"/>
|
||||
<g font-family="DejaVu Sans, sans-serif" fill="#20252b">
|
||||
<text x="72" y="110" font-size="44" font-weight="700">REA</text>
|
||||
<text x="1128" y="106" font-size="24" text-anchor="end" fill="#5b6570">rea.tools</text>
|
||||
<path d="M72 142 H1128" stroke="#dce2e8" stroke-width="2"/>
|
||||
<text x="72" y="252" font-size="64" font-weight="700">Find out how</text>
|
||||
<text x="72" y="334" font-size="64" font-weight="700">software works.</text>
|
||||
<text x="72" y="393" font-size="27" fill="#5b6570">Reverse engineering with your coding agent.</text>
|
||||
<g stroke="#dce2e8" stroke-width="2">
|
||||
<rect x="72" y="469" width="260" height="90" rx="10" fill="#ffffff"/>
|
||||
<rect x="398" y="469" width="324" height="90" rx="10" fill="#f0f5fa" stroke="#294f82"/>
|
||||
<rect x="788" y="469" width="340" height="90" rx="10" fill="#ffffff"/>
|
||||
</g>
|
||||
<g text-anchor="middle" font-size="27">
|
||||
<text x="202" y="525">Your agent</text>
|
||||
<text x="560" y="525" fill="#294f82" font-weight="700">REA + analysis tools</text>
|
||||
<text x="958" y="525">Code & evidence</text>
|
||||
</g>
|
||||
<g stroke="#5b6570" stroke-width="2.5" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M350 514 H380 M372 506 L380 514 L372 522"/>
|
||||
<path d="M740 514 H770 M762 506 L770 514 L762 522"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
|
|
@ -3,11 +3,51 @@
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Dinosaur speed lab — REA</title>
|
||||
<title>Chrome Dinosaur Game: Rebuild Its Speed Rule — REA</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Read the dinosaur game’s speed rule with REA, rebuild it, then change the speed and play."
|
||||
/>
|
||||
<link rel="canonical" href="https://rea.tools/examples/dino-lab/" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:site_name" content="REA" />
|
||||
<meta
|
||||
property="og:title"
|
||||
content="Chrome Dinosaur Game: Rebuild Its Speed Rule — REA"
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Read the dinosaur game’s speed rule with REA, rebuild it, then change the speed and play."
|
||||
/>
|
||||
<meta property="og:url" content="https://rea.tools/examples/dino-lab/" />
|
||||
<meta
|
||||
property="og:image"
|
||||
content="https://rea.tools/assets/social-card.png"
|
||||
/>
|
||||
<meta property="og:image:type" content="image/png" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta
|
||||
property="og:image:alt"
|
||||
content="REA — Find out how software works. Reverse engineering with your coding agent."
|
||||
/>
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta
|
||||
name="twitter:title"
|
||||
content="Chrome Dinosaur Game: Rebuild Its Speed Rule — REA"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="Read the dinosaur game’s speed rule with REA, rebuild it, then change the speed and play."
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image"
|
||||
content="https://rea.tools/assets/social-card.png"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image:alt"
|
||||
content="REA — Find out how software works. Reverse engineering with your coding agent."
|
||||
/>
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<link rel="icon" href="../../assets/favicon.svg" type="image/svg+xml" />
|
||||
<link rel="stylesheet" href="../../assets/styles.css" />
|
||||
|
|
|
|||
|
|
@ -2,6 +2,11 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="robots" content="noindex" />
|
||||
<meta
|
||||
name="description"
|
||||
content="A small Electron Notes app for the REA JavaScript analysis guide."
|
||||
/>
|
||||
<title>Notes — Electron analysis example</title>
|
||||
<script src="renderer.js" defer></script>
|
||||
</head>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,11 @@
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="robots" content="noindex" />
|
||||
<meta
|
||||
name="description"
|
||||
content="A small Notes app for the REA browser and network analysis guide."
|
||||
/>
|
||||
<title>Notes — REA browser example</title>
|
||||
<link rel="icon" href="../../assets/favicon.svg" type="image/svg+xml" />
|
||||
<link rel="stylesheet" href="../../assets/styles.css" />
|
||||
|
|
|
|||
|
|
@ -3,11 +3,51 @@
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>FAQ — REA</title>
|
||||
<title>REA FAQ: Setup, Updates and Troubleshooting</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Answers to common REA questions: installation, updates, analysis tools, coding agents, troubleshooting and sharing examples."
|
||||
/>
|
||||
<link rel="canonical" href="https://rea.tools/faq/" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:site_name" content="REA" />
|
||||
<meta
|
||||
property="og:title"
|
||||
content="REA FAQ: Setup, Updates and Troubleshooting"
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Answers to common REA questions: installation, updates, analysis tools, coding agents, troubleshooting and sharing examples."
|
||||
/>
|
||||
<meta property="og:url" content="https://rea.tools/faq/" />
|
||||
<meta
|
||||
property="og:image"
|
||||
content="https://rea.tools/assets/social-card.png"
|
||||
/>
|
||||
<meta property="og:image:type" content="image/png" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta
|
||||
property="og:image:alt"
|
||||
content="REA — Find out how software works. Reverse engineering with your coding agent."
|
||||
/>
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta
|
||||
name="twitter:title"
|
||||
content="REA FAQ: Setup, Updates and Troubleshooting"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="Answers to common REA questions: installation, updates, analysis tools, coding agents, troubleshooting and sharing examples."
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image"
|
||||
content="https://rea.tools/assets/social-card.png"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image:alt"
|
||||
content="REA — Find out how software works. Reverse engineering with your coding agent."
|
||||
/>
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<link rel="icon" href="../assets/favicon.svg" type="image/svg+xml" />
|
||||
<link rel="stylesheet" href="../assets/styles.css" />
|
||||
|
|
|
|||
|
|
@ -3,11 +3,51 @@
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Your first investigation — REA</title>
|
||||
<title>Your First Reverse Engineering Investigation — REA</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Try REA on a small Notes app. Set up your agent, ask where exported notes go, and check the CSV formatter with a sample title."
|
||||
/>
|
||||
<link rel="canonical" href="https://rea.tools/first-investigation/" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:site_name" content="REA" />
|
||||
<meta
|
||||
property="og:title"
|
||||
content="Your First Reverse Engineering Investigation — REA"
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Try REA on a small Notes app. Set up your agent, ask where exported notes go, and check the CSV formatter with a sample title."
|
||||
/>
|
||||
<meta property="og:url" content="https://rea.tools/first-investigation/" />
|
||||
<meta
|
||||
property="og:image"
|
||||
content="https://rea.tools/assets/social-card.png"
|
||||
/>
|
||||
<meta property="og:image:type" content="image/png" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta
|
||||
property="og:image:alt"
|
||||
content="REA — Find out how software works. Reverse engineering with your coding agent."
|
||||
/>
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta
|
||||
name="twitter:title"
|
||||
content="Your First Reverse Engineering Investigation — REA"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="Try REA on a small Notes app. Set up your agent, ask where exported notes go, and check the CSV formatter with a sample title."
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image"
|
||||
content="https://rea.tools/assets/social-card.png"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image:alt"
|
||||
content="REA — Find out how software works. Reverse engineering with your coding agent."
|
||||
/>
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<link rel="icon" href="../assets/favicon.svg" type="image/svg+xml" />
|
||||
<link rel="stylesheet" href="../assets/styles.css" />
|
||||
|
|
|
|||
|
|
@ -3,11 +3,51 @@
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Get started — REA</title>
|
||||
<title>Install REA and Set Up Your Coding Agent</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Connect REA to your coding agent or inspect a local JavaScript or Electron app from the terminal. Setup, first questions and native provider guides."
|
||||
/>
|
||||
<link rel="canonical" href="https://rea.tools/get-started/" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:site_name" content="REA" />
|
||||
<meta
|
||||
property="og:title"
|
||||
content="Install REA and Set Up Your Coding Agent"
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Connect REA to your coding agent or inspect a local JavaScript or Electron app from the terminal. Setup, first questions and native provider guides."
|
||||
/>
|
||||
<meta property="og:url" content="https://rea.tools/get-started/" />
|
||||
<meta
|
||||
property="og:image"
|
||||
content="https://rea.tools/assets/social-card.png"
|
||||
/>
|
||||
<meta property="og:image:type" content="image/png" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta
|
||||
property="og:image:alt"
|
||||
content="REA — Find out how software works. Reverse engineering with your coding agent."
|
||||
/>
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta
|
||||
name="twitter:title"
|
||||
content="Install REA and Set Up Your Coding Agent"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="Connect REA to your coding agent or inspect a local JavaScript or Electron app from the terminal. Setup, first questions and native provider guides."
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image"
|
||||
content="https://rea.tools/assets/social-card.png"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image:alt"
|
||||
content="REA — Find out how software works. Reverse engineering with your coding agent."
|
||||
/>
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<link rel="icon" href="../assets/favicon.svg" type="image/svg+xml" />
|
||||
<link rel="stylesheet" href="../assets/styles.css" />
|
||||
|
|
|
|||
|
|
@ -3,11 +3,45 @@
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Browser observation — REA</title>
|
||||
<title>Browser and Network Analysis — 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."
|
||||
/>
|
||||
<link rel="canonical" href="https://rea.tools/guides/browser/" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:site_name" content="REA" />
|
||||
<meta property="og:title" content="Browser and Network Analysis — REA" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Observe a Notes export with REA: select a browser page, capture a click, inspect the request and follow the export script."
|
||||
/>
|
||||
<meta property="og:url" content="https://rea.tools/guides/browser/" />
|
||||
<meta
|
||||
property="og:image"
|
||||
content="https://rea.tools/assets/social-card.png"
|
||||
/>
|
||||
<meta property="og:image:type" content="image/png" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta
|
||||
property="og:image:alt"
|
||||
content="REA — Find out how software works. Reverse engineering with your coding agent."
|
||||
/>
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="Browser and Network Analysis — REA" />
|
||||
<meta
|
||||
name="twitter: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="twitter:image"
|
||||
content="https://rea.tools/assets/social-card.png"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image:alt"
|
||||
content="REA — Find out how software works. Reverse engineering with your coding agent."
|
||||
/>
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<link rel="icon" href="../../assets/favicon.svg" type="image/svg+xml" />
|
||||
<link rel="stylesheet" href="../../assets/styles.css" />
|
||||
|
|
|
|||
|
|
@ -3,11 +3,45 @@
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Guides — REA</title>
|
||||
<title>Reverse Engineering Guides — REA</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Learn REA through short worked examples: a native calculation, an Electron CSV export and a browser request."
|
||||
/>
|
||||
<link rel="canonical" href="https://rea.tools/guides/" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:site_name" content="REA" />
|
||||
<meta property="og:title" content="Reverse Engineering Guides — REA" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Learn REA through short worked examples: a native calculation, an Electron CSV export and a browser request."
|
||||
/>
|
||||
<meta property="og:url" content="https://rea.tools/guides/" />
|
||||
<meta
|
||||
property="og:image"
|
||||
content="https://rea.tools/assets/social-card.png"
|
||||
/>
|
||||
<meta property="og:image:type" content="image/png" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta
|
||||
property="og:image:alt"
|
||||
content="REA — Find out how software works. Reverse engineering with your coding agent."
|
||||
/>
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="Reverse Engineering Guides — REA" />
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="Learn REA through short worked examples: a native calculation, an Electron CSV export and a browser request."
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image"
|
||||
content="https://rea.tools/assets/social-card.png"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image:alt"
|
||||
content="REA — Find out how software works. Reverse engineering with your coding agent."
|
||||
/>
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<link rel="icon" href="../assets/favicon.svg" type="image/svg+xml" />
|
||||
<link rel="stylesheet" href="../assets/styles.css" />
|
||||
|
|
|
|||
|
|
@ -3,11 +3,51 @@
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>JavaScript & Electron — REA</title>
|
||||
<title>JavaScript and Electron Analysis — 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."
|
||||
/>
|
||||
<link rel="canonical" href="https://rea.tools/guides/javascript/" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:site_name" content="REA" />
|
||||
<meta
|
||||
property="og:title"
|
||||
content="JavaScript and Electron Analysis — REA"
|
||||
/>
|
||||
<meta
|
||||
property="og: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 property="og:url" content="https://rea.tools/guides/javascript/" />
|
||||
<meta
|
||||
property="og:image"
|
||||
content="https://rea.tools/assets/social-card.png"
|
||||
/>
|
||||
<meta property="og:image:type" content="image/png" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta
|
||||
property="og:image:alt"
|
||||
content="REA — Find out how software works. Reverse engineering with your coding agent."
|
||||
/>
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta
|
||||
name="twitter:title"
|
||||
content="JavaScript and Electron Analysis — REA"
|
||||
/>
|
||||
<meta
|
||||
name="twitter: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="twitter:image"
|
||||
content="https://rea.tools/assets/social-card.png"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image:alt"
|
||||
content="REA — Find out how software works. Reverse engineering with your coding agent."
|
||||
/>
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<link rel="icon" href="../../assets/favicon.svg" type="image/svg+xml" />
|
||||
<link rel="stylesheet" href="../../assets/styles.css" />
|
||||
|
|
|
|||
|
|
@ -3,11 +3,45 @@
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Native analysis — REA</title>
|
||||
<title>Native Binary Analysis — REA</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Recover a native calculation with REA: inspect instructions, follow a caller, read constants and check reconstructed C."
|
||||
/>
|
||||
<link rel="canonical" href="https://rea.tools/guides/native/" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:site_name" content="REA" />
|
||||
<meta property="og:title" content="Native Binary Analysis — REA" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Recover a native calculation with REA: inspect instructions, follow a caller, read constants and check reconstructed C."
|
||||
/>
|
||||
<meta property="og:url" content="https://rea.tools/guides/native/" />
|
||||
<meta
|
||||
property="og:image"
|
||||
content="https://rea.tools/assets/social-card.png"
|
||||
/>
|
||||
<meta property="og:image:type" content="image/png" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta
|
||||
property="og:image:alt"
|
||||
content="REA — Find out how software works. Reverse engineering with your coding agent."
|
||||
/>
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="Native Binary Analysis — REA" />
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="Recover a native calculation with REA: inspect instructions, follow a caller, read constants and check reconstructed C."
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image"
|
||||
content="https://rea.tools/assets/social-card.png"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image:alt"
|
||||
content="REA — Find out how software works. Reverse engineering with your coding agent."
|
||||
/>
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<link rel="icon" href="../../assets/favicon.svg" type="image/svg+xml" />
|
||||
<link rel="stylesheet" href="../../assets/styles.css" />
|
||||
|
|
|
|||
|
|
@ -3,10 +3,50 @@
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>REA — Reverse Engineer Anything</title>
|
||||
<title>REA — Reverse Engineering for Your Coding Agent</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Inspect apps, binaries and runtime behavior with your coding agent or terminal. REA connects local analysis tools through CLI and MCP."
|
||||
content="Reverse engineer apps, binaries and browser behavior with your coding agent. Follow clear guides and try interactive examples with REA."
|
||||
/>
|
||||
<link rel="canonical" href="https://rea.tools/" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:site_name" content="REA" />
|
||||
<meta
|
||||
property="og:title"
|
||||
content="REA — Reverse Engineering for Your Coding Agent"
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Reverse engineer apps, binaries and browser behavior with your coding agent. Follow clear guides and try interactive examples with REA."
|
||||
/>
|
||||
<meta property="og:url" content="https://rea.tools/" />
|
||||
<meta
|
||||
property="og:image"
|
||||
content="https://rea.tools/assets/social-card.png"
|
||||
/>
|
||||
<meta property="og:image:type" content="image/png" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta
|
||||
property="og:image:alt"
|
||||
content="REA — Find out how software works. Reverse engineering with your coding agent."
|
||||
/>
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta
|
||||
name="twitter:title"
|
||||
content="REA — Reverse Engineering for Your Coding Agent"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="Reverse engineer apps, binaries and browser behavior with your coding agent. Follow clear guides and try interactive examples with REA."
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image"
|
||||
content="https://rea.tools/assets/social-card.png"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image:alt"
|
||||
content="REA — Find out how software works. Reverse engineering with your coding agent."
|
||||
/>
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<link rel="icon" href="assets/favicon.svg" type="image/svg+xml" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: https://rea.tools/sitemap.xml
|
||||
|
|
@ -3,11 +3,51 @@
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>How an authenticator generates a login code — REA</title>
|
||||
<title>Aegis APK Reverse Engineering: Login Codes — REA</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Inspect the Aegis Android APK with REA, recover its six-digit login-code calculation and try a reconstruction with an adjustable clock."
|
||||
/>
|
||||
<link rel="canonical" href="https://rea.tools/showcase/aegis/" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:site_name" content="REA" />
|
||||
<meta
|
||||
property="og:title"
|
||||
content="Aegis APK Reverse Engineering: Login Codes — REA"
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Inspect the Aegis Android APK with REA, recover its six-digit login-code calculation and try a reconstruction with an adjustable clock."
|
||||
/>
|
||||
<meta property="og:url" content="https://rea.tools/showcase/aegis/" />
|
||||
<meta
|
||||
property="og:image"
|
||||
content="https://rea.tools/assets/social-card.png"
|
||||
/>
|
||||
<meta property="og:image:type" content="image/png" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta
|
||||
property="og:image:alt"
|
||||
content="REA — Find out how software works. Reverse engineering with your coding agent."
|
||||
/>
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta
|
||||
name="twitter:title"
|
||||
content="Aegis APK Reverse Engineering: Login Codes — REA"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="Inspect the Aegis Android APK with REA, recover its six-digit login-code calculation and try a reconstruction with an adjustable clock."
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image"
|
||||
content="https://rea.tools/assets/social-card.png"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image:alt"
|
||||
content="REA — Find out how software works. Reverse engineering with your coding agent."
|
||||
/>
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<link rel="icon" href="../../assets/favicon.svg" type="image/svg+xml" />
|
||||
<link rel="stylesheet" href="../../assets/styles.css" />
|
||||
|
|
|
|||
|
|
@ -3,11 +3,51 @@
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Solve a CTF flag checker — REA</title>
|
||||
<title>CTF Reverse Engineering: DownUnderCTF Flag Checker — REA</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Use REA to solve DownUnderCTF's masked squares flag checker. Find the checking functions, extract the masks and compare a recovered flag with the original program."
|
||||
/>
|
||||
<link rel="canonical" href="https://rea.tools/showcase/ctf/" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:site_name" content="REA" />
|
||||
<meta
|
||||
property="og:title"
|
||||
content="CTF Reverse Engineering: DownUnderCTF Flag Checker — REA"
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Use REA to solve DownUnderCTF's masked squares flag checker. Find the checking functions, extract the masks and compare a recovered flag with the original program."
|
||||
/>
|
||||
<meta property="og:url" content="https://rea.tools/showcase/ctf/" />
|
||||
<meta
|
||||
property="og:image"
|
||||
content="https://rea.tools/assets/social-card.png"
|
||||
/>
|
||||
<meta property="og:image:type" content="image/png" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta
|
||||
property="og:image:alt"
|
||||
content="REA — Find out how software works. Reverse engineering with your coding agent."
|
||||
/>
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta
|
||||
name="twitter:title"
|
||||
content="CTF Reverse Engineering: DownUnderCTF Flag Checker — REA"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="Use REA to solve DownUnderCTF's masked squares flag checker. Find the checking functions, extract the masks and compare a recovered flag with the original program."
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image"
|
||||
content="https://rea.tools/assets/social-card.png"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image:alt"
|
||||
content="REA — Find out how software works. Reverse engineering with your coding agent."
|
||||
/>
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<link rel="icon" href="../../assets/favicon.svg" type="image/svg+xml" />
|
||||
<link rel="stylesheet" href="../../assets/styles.css" />
|
||||
|
|
|
|||
|
|
@ -3,11 +3,51 @@
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Rebuilding DX-Ball — REA</title>
|
||||
<title>DX-Ball Reverse Engineering: Sound Panning — REA</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Follow a DX-Ball reconstruction from binary analysis through REA to maintained C, original-x86 tests and matching compiled function bytes."
|
||||
/>
|
||||
<link rel="canonical" href="https://rea.tools/showcase/dx-ball/" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:site_name" content="REA" />
|
||||
<meta
|
||||
property="og:title"
|
||||
content="DX-Ball Reverse Engineering: Sound Panning — REA"
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Follow a DX-Ball reconstruction from binary analysis through REA to maintained C, original-x86 tests and matching compiled function bytes."
|
||||
/>
|
||||
<meta property="og:url" content="https://rea.tools/showcase/dx-ball/" />
|
||||
<meta
|
||||
property="og:image"
|
||||
content="https://rea.tools/assets/social-card.png"
|
||||
/>
|
||||
<meta property="og:image:type" content="image/png" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta
|
||||
property="og:image:alt"
|
||||
content="REA — Find out how software works. Reverse engineering with your coding agent."
|
||||
/>
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta
|
||||
name="twitter:title"
|
||||
content="DX-Ball Reverse Engineering: Sound Panning — REA"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="Follow a DX-Ball reconstruction from binary analysis through REA to maintained C, original-x86 tests and matching compiled function bytes."
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image"
|
||||
content="https://rea.tools/assets/social-card.png"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image:alt"
|
||||
content="REA — Find out how software works. Reverse engineering with your coding agent."
|
||||
/>
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<link rel="icon" href="../../assets/favicon.svg" type="image/svg+xml" />
|
||||
<link rel="stylesheet" href="../../assets/styles.css" />
|
||||
|
|
|
|||
|
|
@ -3,11 +3,51 @@
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Case studies — REA</title>
|
||||
<title>Reverse Engineering Case Studies — REA</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Follow REA investigations through code, figures and demos: an Android authenticator, DX-Ball, Notion, TH04 and a DownUnderCTF flag checker."
|
||||
/>
|
||||
<link rel="canonical" href="https://rea.tools/showcase/" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:site_name" content="REA" />
|
||||
<meta
|
||||
property="og:title"
|
||||
content="Reverse Engineering Case Studies — REA"
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Follow REA investigations through code, figures and demos: an Android authenticator, DX-Ball, Notion, TH04 and a DownUnderCTF flag checker."
|
||||
/>
|
||||
<meta property="og:url" content="https://rea.tools/showcase/" />
|
||||
<meta
|
||||
property="og:image"
|
||||
content="https://rea.tools/assets/social-card.png"
|
||||
/>
|
||||
<meta property="og:image:type" content="image/png" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta
|
||||
property="og:image:alt"
|
||||
content="REA — Find out how software works. Reverse engineering with your coding agent."
|
||||
/>
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta
|
||||
name="twitter:title"
|
||||
content="Reverse Engineering Case Studies — REA"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="Follow REA investigations through code, figures and demos: an Android authenticator, DX-Ball, Notion, TH04 and a DownUnderCTF flag checker."
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image"
|
||||
content="https://rea.tools/assets/social-card.png"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image:alt"
|
||||
content="REA — Find out how software works. Reverse engineering with your coding agent."
|
||||
/>
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<link rel="icon" href="../assets/favicon.svg" type="image/svg+xml" />
|
||||
<link rel="stylesheet" href="../assets/styles.css" />
|
||||
|
|
|
|||
|
|
@ -3,11 +3,51 @@
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Following Notion's clipboard through Electron — REA</title>
|
||||
<title>Notion Electron Reverse Engineering: Clipboard — REA</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Follow Notion's clipboard API through Electron's preload, IPC wrappers and main process, with short code excerpts and a look at its rich clipboard format."
|
||||
/>
|
||||
<link rel="canonical" href="https://rea.tools/showcase/notion/" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:site_name" content="REA" />
|
||||
<meta
|
||||
property="og:title"
|
||||
content="Notion Electron Reverse Engineering: Clipboard — REA"
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Follow Notion's clipboard API through Electron's preload, IPC wrappers and main process, with short code excerpts and a look at its rich clipboard format."
|
||||
/>
|
||||
<meta property="og:url" content="https://rea.tools/showcase/notion/" />
|
||||
<meta
|
||||
property="og:image"
|
||||
content="https://rea.tools/assets/social-card.png"
|
||||
/>
|
||||
<meta property="og:image:type" content="image/png" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta
|
||||
property="og:image:alt"
|
||||
content="REA — Find out how software works. Reverse engineering with your coding agent."
|
||||
/>
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta
|
||||
name="twitter:title"
|
||||
content="Notion Electron Reverse Engineering: Clipboard — REA"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="Follow Notion's clipboard API through Electron's preload, IPC wrappers and main process, with short code excerpts and a look at its rich clipboard format."
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image"
|
||||
content="https://rea.tools/assets/social-card.png"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image:alt"
|
||||
content="REA — Find out how software works. Reverse engineering with your coding agent."
|
||||
/>
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<link rel="icon" href="../../assets/favicon.svg" type="image/svg+xml" />
|
||||
<link rel="stylesheet" href="../../assets/styles.css" />
|
||||
|
|
|
|||
|
|
@ -3,11 +3,51 @@
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>TH04 bullet patterns — REA</title>
|
||||
<title>TH04 Reverse Engineering: DOS Bullet Patterns — REA</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Inspect TH04's original 16-bit DOS code with REA. Follow the assembly and recovered C++ to see how a ring of bullets is spaced and aimed at the player."
|
||||
/>
|
||||
<link rel="canonical" href="https://rea.tools/showcase/th04/" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:site_name" content="REA" />
|
||||
<meta
|
||||
property="og:title"
|
||||
content="TH04 Reverse Engineering: DOS Bullet Patterns — REA"
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Inspect TH04's original 16-bit DOS code with REA. Follow the assembly and recovered C++ to see how a ring of bullets is spaced and aimed at the player."
|
||||
/>
|
||||
<meta property="og:url" content="https://rea.tools/showcase/th04/" />
|
||||
<meta
|
||||
property="og:image"
|
||||
content="https://rea.tools/assets/social-card.png"
|
||||
/>
|
||||
<meta property="og:image:type" content="image/png" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta
|
||||
property="og:image:alt"
|
||||
content="REA — Find out how software works. Reverse engineering with your coding agent."
|
||||
/>
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta
|
||||
name="twitter:title"
|
||||
content="TH04 Reverse Engineering: DOS Bullet Patterns — REA"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="Inspect TH04's original 16-bit DOS code with REA. Follow the assembly and recovered C++ to see how a ring of bullets is spaced and aimed at the player."
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image"
|
||||
content="https://rea.tools/assets/social-card.png"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image:alt"
|
||||
content="REA — Find out how software works. Reverse engineering with your coding agent."
|
||||
/>
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<link rel="icon" href="../../assets/favicon.svg" type="image/svg+xml" />
|
||||
<link rel="stylesheet" href="../../assets/styles.css" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
# SVG-to-PNG conversion for the generated social preview.
|
||||
CairoSVG==2.9.1
|
||||
|
|
@ -55,6 +55,13 @@ comes from, or what a check covers. Scope a claim positively and precisely:
|
|||
“The compiled function matches 63 bytes” is more useful than a broad claim
|
||||
followed by several qualifications.
|
||||
|
||||
Search titles and sharing previews follow this standard too. Name the software
|
||||
or task so someone can understand the page before opening it. Keep titles and
|
||||
descriptions accurate and concise; use technical terms when they identify the
|
||||
actual topic. Preserve a clear visible headline rather than repeating search
|
||||
terms throughout the page. See [README.md](README.md#search-and-sharing-metadata)
|
||||
for canonical URLs, automatic sitemap generation and sharing assets.
|
||||
|
||||
## Build a page around one question
|
||||
|
||||
### Introductions and first exercises
|
||||
|
|
|
|||
|
|
@ -20,3 +20,8 @@ name = "rea-website"
|
|||
workers_dev = true
|
||||
preview_urls = false
|
||||
routes = [{ pattern = "rea.tools", custom_domain = true }]
|
||||
|
||||
# Production receives the already prepared and verified shared Pages artifact.
|
||||
# Override the preview build so deployment does not regenerate local assets.
|
||||
[env.production.build]
|
||||
command = ""
|
||||
|
|
|
|||
Loading…
Reference in New Issue