Implements the subset of the isitagentready.com checklist that maps to
real capabilities of this site:
- RFC 8288 Link headers on every browser response advertising
service-doc (/algo), about, privacy-policy, and sitemap.
- Markdown-for-Agents content negotiation: requests with
Accept: text/markdown for / or /algo return real markdown
(curated summary + verbatim algo.md) with x-markdown-tokens hint.
Other paths still 406 — no synthesizing markdown from LiveView HTML.
- Content-Signal directive in robots.txt declaring
search=yes, ai-train=no, ai-input=no.
- RFC 9727 API catalog at /.well-known/api-catalog with the sole
public endpoint (/api/contacts/map) linking status -> /health and
service-desc -> /openapi.json (a new minimal OpenAPI 3.0 spec).
- Agent Skills Discovery index at /.well-known/agent-skills/index.json
listing two skill documents (fetch-contacts, read-algorithm) with
sha256 digests computed at compile time; documents served at
/.well-known/agent-skills/{name}/SKILL.md.
Skipped (don't match actual site capabilities): Web Bot Auth JWKS,
OAuth/OIDC discovery, MCP server card.
72 lines
2.3 KiB
Elixir
72 lines
2.3 KiB
Elixir
defmodule MicrowavepropWeb.AgentSkillsController do
|
|
@moduledoc """
|
|
Publishes an Agent Skills Discovery (v0.2.0) index at
|
|
`/.well-known/agent-skills/index.json` plus the referenced skill
|
|
documents. Each document is a short markdown file describing one
|
|
concrete task an agent can perform against this service's public
|
|
endpoints. Digests are computed at compile time from the source files.
|
|
"""
|
|
use MicrowavepropWeb, :controller
|
|
|
|
@schema_url "https://agentskills.io/schemas/v0.2.0/skills.schema.json"
|
|
|
|
@skill_sources [
|
|
%{
|
|
name: "fetch-contacts",
|
|
description:
|
|
"Retrieve every amateur-radio contact in the propagation calibration dataset as a JSON array of compact tuples.",
|
|
path: "priv/agent-skills/fetch-contacts.md"
|
|
},
|
|
%{
|
|
name: "read-algorithm",
|
|
description:
|
|
"Obtain the full NTMS propagation scoring algorithm documentation, including factor weights and ITU-R model references, as markdown.",
|
|
path: "priv/agent-skills/read-algorithm.md"
|
|
}
|
|
]
|
|
|
|
for %{path: path} <- @skill_sources do
|
|
@external_resource path
|
|
end
|
|
|
|
@skills Enum.map(@skill_sources, fn %{name: name, description: desc, path: path} ->
|
|
body = File.read!(path)
|
|
sha = :sha256 |> :crypto.hash(body) |> Base.encode16(case: :lower)
|
|
%{name: name, description: desc, body: body, sha256: sha}
|
|
end)
|
|
|
|
@spec index(Plug.Conn.t(), map()) :: Plug.Conn.t()
|
|
def index(conn, _params) do
|
|
entries =
|
|
Enum.map(@skills, fn s ->
|
|
%{
|
|
name: s.name,
|
|
type: "text/markdown",
|
|
description: s.description,
|
|
url: url(conn, ~p"/.well-known/agent-skills/#{s.name}/SKILL.md"),
|
|
sha256: s.sha256
|
|
}
|
|
end)
|
|
|
|
body = Jason.encode!(%{"$schema" => @schema_url, skills: entries})
|
|
|
|
conn
|
|
|> put_resp_content_type("application/json")
|
|
|> put_resp_header("cache-control", "public, max-age=3600")
|
|
|> send_resp(200, body)
|
|
end
|
|
|
|
@spec skill(Plug.Conn.t(), map()) :: Plug.Conn.t()
|
|
def skill(conn, %{"name" => name}) do
|
|
case Enum.find(@skills, &(&1.name == name)) do
|
|
nil ->
|
|
send_resp(conn, 404, "")
|
|
|
|
s ->
|
|
conn
|
|
|> put_resp_content_type("text/markdown", "utf-8")
|
|
|> put_resp_header("cache-control", "public, max-age=3600")
|
|
|> send_resp(200, s.body)
|
|
end
|
|
end
|
|
end
|