Reveal
What isn't the page showing you?
A read gives you the text. That's its job. It's also exactly how the clue gets lost. Read https://gsmg.io/theseedisplanted and you get one line, # GSMG Puzzle. That's it.
revealUrl fetches the page's own HTML and lists what a browser keeps out of sight.
import { revealUrl } from "@agntn/web";
const { findings } = await revealUrl("https://gsmg.io/theseedisplanted");
for (const finding of findings) {
console.log(finding.kind, finding.line, finding.value ?? finding.reason);
}
Same page from the terminal:
web reveal https://gsmg.io/theseedisplanted
[reveal] https://gsmg.io/theseedisplanted
text/html; charset=utf-8 · 832 bytes
Found 1 comment, 1 hidden element, 2 form fields, 4 meta tags, 1 link
Comments
line 12 in html.no-js: Nice to see you around! Good luck little bunny hunter ;)
Hidden elements
line 23 div (display: none): (no text)
Form fields
line 23 input password (type=password, inside hidden div at line 23)
line 23 input (no name) (type=submit, inside hidden div at line 23)
Meta tags
line 5 charset: utf-8
line 7 robots: noindex, nofollow
line 8 description: GSMG Puzzle
line 9 viewport: width=device-width, initial-scale=1
Links
line 6 icon https://gsmg.io/img/favicon.png
There it is. The first step of the GSMG puzzle, a comment and a password form behind display: none. Good luck, little bunny hunter.
What counts as hidden
| Kind | What it lists |
|---|---|
comment | HTML comments, plus <?...?> and <![CDATA[...]]>, which browsers keep as comments too |
hidden | display: none, visibility: hidden, zero opacity or font size, the hidden attribute, a closed <dialog>, <template>, text pushed off screen or clipped to nothing |
field | fields of type="hidden", and every field inside a hidden element |
camouflage | text in its background's color |
invisible | zero width and other invisible characters, by code point |
attribute | data-* and attributes HTML doesn't define |
meta, link | meta and link tags |
script, style | inline code as an excerpt, linked files as URLs |
Every finding carries its line in the source and the element around it. The table order is the answer's order, so the things nobody sees come first. Hidden text spelled in tag characters or variation selectors gets decoded. Sneaky. Not anymore.
Wayback captures come raw
Pass a web.archive.org/web/<timestamp>/<url> address and it reads the id_ form instead. That's the capture as archived. No toolbar, no Wayback scripts posing as the page's own. Relative links resolve against the archived page, not the archive.
The Wayback Machine is slow, and sometimes it answers 503 for a minute. A 503 gets two more tries. A timeout doesn't, it already took its minute.
Public pages only
localhost, 10.0.0.8, 169.254.169.254 and the rest of the private ranges get refused before a single request. Every redirect is checked too. So a model holding this tool can't aim it at your router or your cloud metadata. Your own code can, with allowPrivateNetwork: true.
Stylesheets count too
A .secret { display: none } rarely sits in the page. It lives in site.css. So the reveal reads the linked sheets too. Plus whatever they @import, cascaded the way a browser does it. Ten at most. Each one passes the same address check and byte limit as the page, and all of them get 30 seconds together. A sheet that 404s or comes back as HTML doesn't count, same as in a browser. A Wayback capture gets its sheets from the same capture, not from today's site.
Cascade layers count as well. Tailwind v4 tucks nearly all its CSS into @layer, .hidden and .sr-only included. Skip those blocks and the reveal goes as blind as the reader. So layer blocks, @layer a, b; statements and layer() imports rank the way a browser ranks them. Nested layers too, and !important still turns the order around.
And @media? A block every screen matches counts like the rules around it: screen, all, not print, or no query at all. print stays out, and so does anything about width. So Bootstrap's .d-md-none stays a mystery. It lives in (min-width: 768px), and the reveal has no window to measure.
Which ones made it? stylesheets in the result lists them, and web_reveal says how many it applied.
What it can't see
Nothing runs. No JavaScript. Selectors stay simple: a tag, an id, some classes. nav a:hover gets skipped, and so does any @media waiting for a window size, a printer or a reader's setting. Colors count only when the page or its sheets declare them. A page that builds itself in the browser needs a browser, and that's @agntn/browsers.
For agents
web_reveal takes a url, plus maxChars (20 000 by default) and timeoutSeconds. When the findings don't fit, the answer says what it left out by kind. Then the model knows to ask for more, right?