Oratos for Elixir & Phoenix
Website visibility audits for SEO, accessibility, structured data (JSON-LD), and LLM readiness — run from Mix against the HTML you actually ship.
Oratos is a native CLI managed by this Hex package. On first use it downloads a prebuilt binary from GitHub Releases. No Rust toolchain is required.
This package does not audit HTML inside the BEAM, and it does not replace
phoenix_seoor your templates. Use those to emit metadata; use Oratos to score the rendered output (like Credo/Sobelow for visibility quality).
Install
# mix.exs — audit tooling only; omit from production releases
defp deps do
[
{:oratos, "~> 0.3.1", only: [:dev, :test], runtime: false}
]
end
mix deps.get
First mix oratos.audit (or mix oratos --version) downloads the CLI into the package priv/bin/ directory.
Phoenix workflow
Oratos audits rendered HTML, typically under priv/static after assets + prerender:
mix assets.deploy
mix phx.digest
mix phoenix.prerender # or your static export step
mix oratos.audit ./priv/static --fail-under 85
If you skip prerender and only have LiveView HTML, Oratos can still audit any directory of .html files you produce (e.g. a mix task that writes snapshots). The CLI needs files on disk (or a URL with --crawl).
Mix aliases
# mix.exs
def project do
[
# ...
aliases: aliases()
]
end
defp aliases do
[
# One string per Mix invocation so flags are args, not separate tasks
"seo.audit": "oratos.audit priv/static --fail-under 85",
"seo.report": "oratos.audit priv/static --format html --output reports/oratos.html",
"seo.strict": "oratos.audit priv/static --strict --fail-under 85"
]
end
mix seo.audit
mix seo.report
Mix tasks
mix oratos.audit
Runs oratos audit. Prefer this in apps and CI.
# defaults to priv/static (or dist if present)
mix oratos.audit
mix oratos.audit ./priv/static --fail-under 85
mix oratos.audit ./priv/static --strict
mix oratos.audit ./priv/static --format sarif --output reports/oratos.sarif
mix oratos.audit ./priv/static --format json --output reports/oratos.json
mix oratos.audit ./priv/static --changed-only
mix oratos.audit ./priv/static --config oratos.toml
Useful flags:
| Flag | Purpose |
|---|---|
--fail-under SCORE | Non-zero exit if overall score < SCORE (CI gate) |
--strict | Fail on any warning or error finding |
--format console|json|markdown|html|sarif | Report format |
--output PATH | Write report to a file |
--changed-only | Only HTML changed in git diff HEAD |
--config PATH | Load oratos.toml (ignore rules, crawl, defaults) |
--crawl | Multi-page crawl when the target is a URL |
mix oratos
Forwards arguments to the CLI (downloads binary if needed):
mix oratos --version
mix oratos --help
mix oratos generate llms ./priv/static --output priv/static/llms.txt
mix oratos prompt phoenix priv/static/index.html --output tmp/remediate.md
mix oratos prompt html priv/static/about.html
What Oratos checks
Scores are 0–100 per category (weighted overall: SEO 30%, Accessibility 25%, Structured Data 25%, LLM 20%).
- SEO — title, meta description, canonical, headings, Open Graph, Twitter cards, internal links
- Accessibility —
lang, alt text, landmarks, form labels, link text - Structured data — JSON-LD presence/validity, WebPage / breadcrumbs / Article / Organization hints
- LLM readiness —
llms.txt, extractable text, summaries, image descriptions
Findings use stable rule_ids (see the rule catalog).
Configuration (oratos.toml)
Optional project file at the site root (or pass --config):
[audit]
fail_under = 85
strict = false
ignore_rules = ["seo.missing-twitter-card"]
changed_only = false
[crawl]
enabled = false
max_pages = 50
CLI flags override file values. Full schema: configuration docs.
Application config (this Hex package)
Controls how the CLI binary is obtained, not audit rules:
# config/config.exs
config :oratos,
# CLI release to download (without leading "v")
version: "0.3.1",
# Use an existing binary instead of downloading:
# path: "/usr/local/bin/oratos",
# Prefer `oratos` on PATH when present:
prefer_path: false
CI (GitHub Actions)
- uses: actions/checkout@v4
- uses: erlef/setup-beam@v1
with:
elixir-version: "1.17"
otp-version: "27"
- run: mix deps.get
- run: mix assets.deploy && mix phx.digest && mix phoenix.prerender
# Option A — Mix task (downloads CLI into deps/oratos/priv)
- run: mix oratos.audit ./priv/static --fail-under 85 --format sarif --output oratos.sarif
# Option B — install CLI on PATH (no Hex download step)
# - uses: latentmeta/oratos/.github/actions/setup-oratos@v0.3.1
# - run: oratos audit ./priv/static --fail-under 85
- uses: actions/upload-artifact@v4
with:
name: oratos-sarif
path: oratos.sarif
Relationship to phoenix_seo
| Tool | Role |
|---|---|
| phoenix_seo (and friends) | Help your app render titles, meta, JSON-LD |
| Oratos | Audit the exported HTML and gate CI on scores/findings |
Typical loop: improve metadata in Phoenix → prerender/export → mix oratos.audit → fix findings (or generate a remediation prompt with mix oratos prompt phoenix …).
Programmatic access
{:ok, path} = Oratos.ensure_binary()
System.cmd(path, ["audit", "priv/static", "--format", "json"], stderr_to_stdout: true)
How the binary is obtained
This Hex package is a thin Mix wrapper. The first time you run mix oratos or
mix oratos.audit, it downloads a prebuilt oratos executable from GitHub
Releases into deps/oratos/priv/bin/ (cached for later runs).
Fetch and unpack use Req (Req.Tar / Req.ZIP) —
no curl, tar, or unzip. You only need outbound HTTPS to GitHub on that
first run (plus git if you use --changed-only, which diffs against HEAD).
Skip the download by pointing at a CLI you already installed (Homebrew,
install.sh, etc.):
config :oratos, path: "/opt/homebrew/bin/oratos"
# or: prefer_path: true # use `oratos` from PATH when available
Further reading
- Install matrix (Homebrew, mise, pip, npm, …)
- Phoenix workflows
- CI/CD
- Scoring
- GitHub · crates.io CLI