Packages

Headless local MCP node providing notebook, references and script capability to LLM workers

Current section

Files

Jump to
ymer_node mix.exs
Raw

mix.exs

defmodule YmerNode.MixProject do
use Mix.Project
def project do
[
app: :ymer_node,
version: "0.2.2",
elixir: "~> 1.19",
elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod,
aliases: aliases(),
deps: deps(),
releases: releases(),
description:
"Headless local MCP node providing notebook, references and script capability " <>
"to LLM workers",
package: package(),
name: "Ymer Node",
docs: docs()
]
end
def application do
[
mod: {YmerNode.Application, []},
# `:xmerl` is named here although sweet_xml's own application closure
# already carries it: XPath over `:xmerl` is a promise the node makes in
# `YmerNode.Script`'s What a script may call, so the application is the
# node's to name rather than a dependency's side effect — the same reason
# jsv below is a direct dependency. A release ships only the OTP
# applications its closure names, and this line is what keeps `:xmerl`
# in it if that closure ever stops needing it. `:eex` is named for the
# same reason and one more: typst renders every document through
# `EEx.eval_string/3` while its own application names no `:eex`, which
# reaches the release today only through other dependencies' closures,
# plug's and ecto's among them — so this line is what keeps every render
# working if those ever stop needing it.
extra_applications: [:logger, :xmerl, :eex]
]
end
def cli do
[preferred_envs: [precommit: :test]]
end
# The repository's published home, named once: hex requires `links`, and
# ExDoc's `source_url` below is what gives every module page on hexdocs its
# "view source" link. Both point here so the two cannot drift apart.
@source_url "https://github.com/kristiangronberg/ymer-node"
defp package do
[
licenses: ["Apache-2.0"],
links: %{"GitHub" => @source_url},
files: ~w(lib priv .formatter.exs mix.exs README.md LICENSE)
]
end
defp docs do
[
main: "readme",
source_url: @source_url,
extras: [{"README.md", title: "README"}, "docs/glossary.md", "docs/scripts.md"],
groups_for_modules: [Scripts: ~r/^YmerNode\.Scripts?(\.|$)/]
]
end
defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]
defp deps do
[
{:bandit, "~> 1.12.5"},
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
{:ecto_sql, "~> 3.13"},
{:ecto_sqlite3, "~> 0.25"},
{:elixlsx, "~> 0.6.0"},
{:ex_doc, "~> 0.40.4", only: [:dev, :test], runtime: false},
{:ex_slop, "~> 0.4.5", only: [:dev, :test], runtime: false},
# HTML a script reads into markdown — a wiki page, a mail body. Pure
# Elixir, with no dependencies of its own.
{:floki, "~> 0.38.4"},
# Direct although wymcp already locks it transitively: the node validates a
# run's arguments against the action's own schema, so jsv is a dependency
# the node relies on and therefore names. The alternative — wymcp's
# public but undocumented `validate_schema/2` — would bind us to an
# internal, which is a different thing from relying on a transitive
# requirement the way the :plug comment below describes.
{:jsv, "~> 0.22"},
# Markdown a script turns into what a system accepts — HTML, or a tree a
# renderer walks into a wiki's markup. A NIF: the build downloads its
# precompiled artifact for the building machine's target, so no Rust
# toolchain reaches the image.
{:mdex, "~> 0.13.5"},
{:mix_audit, "~> 2.1", only: [:dev, :test], runtime: false},
{:mix_test_watch, "~> 1.0", only: :dev, runtime: false},
# CSV a script reads or writes — `NimbleCSV.RFC4180` ready-made, and a
# separator variant `NimbleCSV.define/2` builds as a module nested under
# the script's (`__MODULE__.Semi`), so the compiler's purge reaches it.
# Pure Elixir, with no dependencies of its own; with it in the
# release Req's opt-in `:csv` decoder works too, which is Req's option
# and not a promise the node makes.
{:nimble_csv, "~> 1.3"},
# The HTTP battery every script receives through its context. Runtime, and
# for a caller's sake rather than the node's: a script's whole point is
# reaching a system, and the node itself never makes an outbound call.
{:req, "~> 0.7.4"},
# XML a script writes — a SOAP envelope built with `Saxy.XML` and encoded
# with `Saxy.encode!/2`. Pure Elixir, no dependencies, and in the release
# regardless as xlsx_reader's parser; named here because writing XML is a
# job the node promises it for.
{:saxy, "~> 1.6"},
# XML a script reads — an RSS feed, a SOAP answer — as XPath over OTP's
# `:xmerl`, which `application/0` names above. Pure Elixir, with no
# dependencies of its own.
{:sweet_xml, "~> 0.7.5"},
{:typst, "~> 0.4.4"},
# The zone database behind every zone-aware `DateTime` call a script makes,
# made Elixir's own by one line in `config/config.exs`. The IANA data is
# compiled into the package, so the node makes no call for zone data:
# castore and mint serve only tz's updater, which nothing here starts —
# mint is in the tree for Req, castore is not in it at all. The zones are
# the IANA release this tz bundles, and IANA moves faster than tz does, so
# a change that touches this file may take a newer tz. Should a zone in use
# change before tz catches up, vendor a pinned IANA version instead —
# `mix tz.download <version>` into `config :tz, :data_dir`, with
# `:iana_version` — and move the Dockerfile's `COPY priv` above its
# `mix deps.compile`, which today runs first.
{:tz, "~> 0.28.2"},
# No direct :plug dep although `YmerNode.Mcp.Endpoint` uses `Plug.Router`:
# bandit and wymcp are both built around Plug and pull it in — the
# transitive requirement is relied on deliberately, not restated here.
# Three-segment on purpose: `~> 0.8` would float onto 0.9.0, and pre-1.0
# wymcp treats a minor as the breaking bump, so `~> 0.8.0` admits 0.8.x
# only. Plain hex in every environment — a path dep to a sibling checkout
# would make the build depend on this machine's layout, and the clone
# gate exists to prove it does not.
{:wymcp, "~> 0.8.2"},
# The XLSX a script reads — a spreadsheet a colleague dropped in the files
# directory. Pure Elixir; it parses through saxy, and reads numbers as
# floats unless told otherwise, which `YmerNode.Script`'s What a script
# may call says beneath its table.
{:xlsx_reader, "~> 0.8.12"}
]
end
defp releases do
[
ymer_node: [
include_executables_for: [:unix],
# The `Docs` chunk is what `YmerNode.Scripts.Guide` renders the guide
# from at the call; a release strips it by default, and the guide then
# refuses rather than answering in part. Release-wide, like the strip.
strip_beams: [keep: ["Docs"]]
]
]
end
defp aliases do
[
precommit: [
"compile --warnings-as-errors",
"deps.unlock --unused",
"format",
"credo --strict",
"deps.audit",
"docs --warnings-as-errors",
"test --warnings-as-errors"
]
]
end
end