Oratos for Elixir & Phoenix

Copy Markdown View Source

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

FlagPurpose
--fail-under SCORENon-zero exit if overall score < SCORE (CI gate)
--strictFail on any warning or error finding
--format console|json|markdown|html|sarifReport format
--output PATHWrite report to a file
--changed-onlyOnly HTML changed in git diff HEAD
--config PATHLoad oratos.toml (ignore rules, crawl, defaults)
--crawlMulti-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
  • Accessibilitylang, alt text, landmarks, form labels, link text
  • Structured data — JSON-LD presence/validity, WebPage / breadcrumbs / Article / Organization hints
  • LLM readinessllms.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

ToolRole
phoenix_seo (and friends)Help your app render titles, meta, JSON-LD
OratosAudit 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