See what
they see
.
Search engines and AI answer-engines read your page very differently from a human. Sjá shows you exactly what each of them sees — then turns every finding into a fix, and lets an AI agent apply it in your actual files. Pages are fetched and analyzed on your machine; your projects never leave it.
Apple Silicon macOS · 3-day trial, then a license code · offline activation
Old Norse
sjá
/sjaː/ · verb
“to see” — to look, to perceive, to understand.
The Old Norse sjá is the ancestor of the Norwegian se and the English see. It carries both meanings the word still has today: to look at something, and to understand it. In the sagas it is also the verb of the seer — the one who perceives what others cannot.
That is the job. A crawler and a language model both “read” your page, but neither reads it the way you wrote it: one sees markup, headings and link graphs; the other looks for claims it can quote and attribute. Sjá makes both views visible — and actionable.
It joins the rest of the Norse-named Elyra family: Askr, the world-tree that carries the load, and Ratatosk, the squirrel that runs messages up and down it.
We were tired of
five apps and a spreadsheet.
Auditing one page used to mean opening a browser extension for the meta tags, a web tool for the structured data, a crawler for the site, PageSpeed in another tab, a spreadsheet to remember last month's score — and then a chat window to ask a model how to fix any of it. Six tools, four subscriptions, no memory, and every finding copy-pasted by hand.
So we built the app we wanted: everything in one place. Analysis, crawling, structured data, Core Web Vitals, history, trends, monitoring, AI fixes and client-ready reports — one native binary on your own machine, with your projects in a local database.
One app, not six
Audit, crawl, structured data, Core Web Vitals, history, monitoring and reporting share one workspace — and one mental model. No exporting between tools.
Local-first, not a SaaS
Pages are fetched and analyzed on your machine; projects and history live in a local SQLite database. No crawl budget, no seat-based dashboard, no data leaving your laptop.
Findings become fixes
Every warning is one click from a context-aware fix prompt — and, with your code folder connected, from an AI agent that edits the real files behind a confirmation step.
Ranking is not the
only game any more.
More and more of your traffic never sees a results page. Someone asks an assistant, and the assistant answers — citing whichever pages were legible and quotable enough to use. That is Generative Engine Optimization, and it is scored differently from classic SEO.
Sjá grades it as a first-class category: JSON-LD structured data, llms.txt, answer-style headings that map to real questions, and citable schema types — Article, FAQPage, HowTo — that let a model quote you with attribution.
And since 0.5.0 it stops predicting and starts measuring: point it at your access log and it reports which AI crawlers actually came — and whether your own robots.txt is quietly turning them away. Then it writes the missing pieces from pages it really crawled.
Scored categories
Weighted checks across every category, an overall score (0–100) and a grade (A–F). Pick an objective — audit, get indexed, improve AI visibility — and the weighting shifts toward the signals that matter for that goal. A category that measured nothing is left out rather than scored a flattering 100.
From one page to
the whole site.
Page analysis
A keyword, a URL and an objective. Sjá fetches the page, parses the HTML and returns weighted checks across six categories — seven once PageSpeed adds Core Web Vitals — with an overall score and grade, every finding backed by the actual value it found. Since 0.6.0 the GEO category is built along the path a page actually travels — access (may the answer crawlers fetch it, and does the server let them when they ask), content (what the engine extracts, where its reading budget ends, which passages it could quote), and outcome (is the page cited, did the crawlers come and keep coming) — fifteen checks that decide roughly half the score under Improve AI visibility. Scores moved between 0.5.1 and 0.6.0: up for pages the crawlers can reach and read, down for pages behind a bot-blocking CDN or a JavaScript shell. Which is the point.
hreflang, validated
Misconfigured hreflang fails silently — search engines discard the whole annotation set without reporting an error. Sjá checks BCP 47 syntax (catching en_US, en-UK, en-EU), self-reference, absolute URLs, conflicting targets, x-default and canonical agreement. A single-market page is never penalized. Analyze as a market with Language + Market, sent as Accept-Language.
Search Console, your own client
Connect Google Search Console with a credential from your own Google Cloud project — PKCE, a loopback redirect open only for the duration of the flow, refresh token in the Keychain. List the properties an account can read, one account per app or per project, so an agency keeps each client's Google access isolated.
Core Web Vitals
Enable PageSpeed to add a sixth category: performance score, LCP, CLS, TBT (an INP proxy) and FCP. If the API is unavailable it degrades to a neutral note instead of hurting your score.
Fix-prompt builder
Click any warning to append a context-aware, copy-paste-ready fix prompt — with the page URL, keyword and real values — to a stack. Copy the lot, or hand it to the agent.
AI fixes in your files
Connect a project to a local code folder and the agent gets sandboxed file tools scoped to it. It explores, reads and edits the real files, streaming its work — pausing for a preview and your approval before each write.
Site crawl
Seed a URL and Sjá discovers pages from robots.txt sitemaps and internal links, respects disallow rules with a politeness delay, then aggregates a site score, an issue inventory, broken pages and duplicate titles/descriptions. Stop it mid-flight and everything crawled so far is saved, badged as stopped early so a partial crawl is never mistaken for a complete one.
Trends & comparison
Every run is kept per project. Watch score-over-time with a sparkline and the delta since the first run, then diff two runs to see exactly which checks were fixed and which regressed.
Monitors
Re-scan URLs on a schedule — hourly, 6-hourly, daily, weekly. A score drop of 5 or more (or a fetch error) raises an alert with a native notification, and the bell counts what you have not read.
Did the AI crawlers actually come?
Point Sjá at an nginx or Apache access log and it reports which AI crawlers really visited: hits, distinct pages, status split, first and last seen, top paths — and each crawler's own robots.txt verdict. Answer engines are separated from training and search crawlers, because blocking the first means absence from answers today. Logs are read where they lie, never copied and never uploaded: an access log is full of visitor IP addresses. 0.6.0 keeps visits per crawler per day, so Did ClaudeBot come back after we changed robots.txt? is a chart in Trends rather than a guess. And because a User-Agent is a string anyone can send, Verify addresses compares the log's client addresses with the ranges each operator publishes and says per crawler whether every visit came from them, some did, or none did. Operators that publish no list — Anthropic among them — are unverifiable, never verified. The lists are cached locally; no address from the log leaves the machine.
The robots.txt trap it found
Sjá used to read only the User-agent: * group — so a site that allows * and disallows GPTBot, which is the most common way to be invisible to answer engines, was reported as not blocked. The parser now reads every group and resolves the most specific match, the crawler obeys rules written for its own token, and Crawl-delay is read from the group that actually governs it. But robots.txt is advice. Since 0.6.0 the page is fetched a second time with OAI-SearchBot's real User-Agent, because that is the only way to see a CDN's block-AI-bots switch, a WAF rule or a bot challenge — Cloudflare's Just a moment is recognised even on a 200. Blocking an answer crawler is a fail: they fetch pages to answer questions now. Blocking a training crawler is a weight-zero note: a licensing choice, not a visibility problem. And a page with under fifty words of HTML behind an empty app root fails too — AI crawlers execute nothing.
Cited by answer engines? Ask them
Everything else in GEO predicts; the citation probe asks. From a page's report, type the question a user would ask and Sjá puts it to ChatGPT, Claude and Perplexity with your own keys, concurrently, and judges each answer: does it cite the page's site, at which position among the sources, and who was cited instead. Every probe is a row — failures included, so a gap in the history is visible as a gap — and Trends shows the latest verdict per page and engine with the earlier ones as dots. The interface says what a probe is: one sample of a non-deterministic system, through APIs close to but not identical with the consumer products, at a few cents per engine on your own account. What to read is the trend and the competitor list, not one answer.
What the model sees
Engines do not read pages. They extract the main content, keep what fits a reading budget, and quote passages. Since 0.6.0 every report carries that view: the extracted text in reading order with navigation, footers, cookie banners and sidebars removed the way a readability extractor removes them, a marker at roughly 2 000 tokens with every block knowing whether it starts inside it, and the five passages most likely to be quoted, each scored 0–100 with the reasons on both sides — 40 to 90 words, stands alone, sits directly under a heading, carries a figure or a date, begins inside the budget. The gaps are what a writer changes. The MCP server and the CLI return the same extraction, so an assistant edits the exact text the engine will see.
Generates llms.txt and JSON-LD
Sjá already told you these were missing; now it writes them from pages it actually crawled. llms.txt lists real URLs with their real titles, excluding error pages, untitled pages and anything marked noindex — advertising a page the site tells search engines to ignore would contradict the site itself. FAQPage schema is built from the page's own question headings and the prose beneath them; a page with no Q&A generates nothing, with an explanation, rather than invented questions. Output is stable across runs, so a diff shows content changes rather than reordering. And since 0.6.0 structured data is read for what an engine needs rather than counted: a block that is not valid JSON is a fail that says so instead of reading as no structured data — same outcome for the engine, different fix; required properties per type fail when missing and recommended ones warn; the publisher entity behind the page is named or found absent, and a crawl lists every Organization the site claims to be; and the dates are held to agree — modified before published fails, two modified dates that disagree warn, no dates at all is a note, not a penalty.
A budget your CI can fail on
elyra-sja check https://example.com --budget 80 runs headless: no window, no database, not even the app data directory. Exit codes are the interface — 0 met, 1 a budget missed, 2 could not run — deliberately distinct so a pipeline can tell a score regression from a broken site. Budget per category too (--budget-geo 70), because a GEO regression otherwise hides behind a healthy overall score, and a budget on a category that was never measured fails rather than passes.
An MCP server, so a model can ask
elyra-sja mcp speaks JSON-RPC over stdio, so Claude Desktop, Zed or Cursor can call the analysis while someone edits content — a model driving Sjá rather than the other way round. Two read-only tools: analyze_page, and robots_check for whether a named crawler may fetch a URL. Nothing that writes files or spends an AI key is exposed, because an external agent must not be able to edit a checkout through this door. And since 0.5.1 it refuses local and private targets by default: its URLs come from an assistant that may have been steered by a page it just read, so analyze_page and robots_check reject a local target before any fetch, naming the opt-in — ELYRA_SJA_ALLOW_PRIVATE=1 in the client's env block audits a dev server through an assistant. A valid licence does not lift this. Client-by-client setup, the tool contracts and real output are in the MCP guide in the docs.
Client-ready reports
Print-optimized page and site reports branded with your agency name: scores, category breakdowns, every finding, the issue inventory and the full page table. Export to PDF, or JSON for a page.
Secrets in the Keychain
Your AI API key and license code live in the macOS Keychain — never in the SQLite file, never in a config you might commit.
A sandbox that holds
The agent's file tools are scoped to your project folder and the walk is symlink-safe — including dangling symlinks, the case that quietly defeats an exists() check. And because Sjá fetches URLs it did not choose — redirect targets, the sitemaps a robots.txt names, whatever an assistant hands the MCP server — those requests are vetted by where a host resolves, not only by what the URL looks like. That sentence is true since 0.5.1. Before it the guard ran only inside the redirect policy, so it never saw the first request or the sitemap URLs, and as a check on address literals it was bypassed by any hostname that resolves to a private address. A filtering resolver now drops loopback, private, link-local, CGNAT and IPv6 unique-local answers; the one host you typed yourself is exempt, so auditing localhost:3000 still works, and a redirect from it to a different private name is refused. The same review found the fix agent could not overwrite an existing file at all — a path that already existed gained a trailing separator and every write failed with Not a directory; creating files worked, so the sandbox tests never noticed. Writes are atomic now, temp file beside the target and a rename. Each fix ships with a test that fails against the version before it.
One binary. No Electron.
Sjá is built on the Elyra Framework — Rust on the inside, Svelte 5 on the outside, compiled to a single native binary with no bundled Node runtime and no browser engine to ship. It starts instantly, idles at nothing, and the whole app is one file you can drag to Applications.
- Rust + Svelte 5
- Native binary
- SQLite
- Local projects & history
- Keychain
- Secrets at rest
- Ed25519
- Offline license
Download Sjá
Signed and notarized for Apple Silicon macOS. Every install runs a 3-day trial with full functionality; after that, paste a license code in Settings → License.
Requirements
macOS on Apple Silicon (M1 or newer).
Install
Open the DMG and drag Elyra Sjá to Applications.
Activate
Settings → License → paste your code. Verified offline.
Buy it once a year.
Or once, forever.
One license covers the whole app — every category, the crawler, monitors, AI fixes and branded reports. No per-crawl credits, no feature gates.
Pro
For the person who owns the site.
up to 3 devices
- Every check & category
- Site crawl & issue inventory
- Trends, comparison & monitors
- AI fixes in your own files
- Branded PDF reports
- A year of updates
Studio
For an agency or an in-house team.
up to 10 devices
- Everything in Pro
- Up to 10 devices
- Shared team database Soon
- Client-ready branded reports
- Unlimited projects & monitors
- Priority support
- A year of updates
Lifetime
Pay once. Keep it.
up to 3 devices
- Everything in Pro
- Never expires
- All future updates
- Up to 3 devices
- One-time payment
- Launch offer
One database for the team
Coming soonToday every seat keeps its own local database. On a Studio licence that becomes a choice: point the whole team at one shared database, so projects, history, trends and monitors are the team's — not each laptop's. Shipping in a future update; tracked in the changelog.
Offline activation, no phoning home
Your code is an Ed25519-signed token. Sjá verifies it against a public key baked into the app, so activation works on a plane — and we never see your usage. Details in the licensing guide.
Try before you buy
Every install includes a 3-day trial with full functionality — no card, no account. Already bought? Manage your license and re-download any time from your account.
Stop guessing what
the crawler saw.
One app. Your machine. Every finding traced back to the markup that caused it.