diff --git a/.formatter.exs b/.formatter.exs index 618aa09..cd0d98a 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -3,7 +3,10 @@ # SPDX-License-Identifier: MIT # Used by "mix format" -locals_without_parens = [] +locals_without_parens = [ + tool: 3, + tool: 4 +] [ plugins: [Spark.Formatter], @@ -11,6 +14,7 @@ locals_without_parens = [] import_deps: [ :diffo, :ash, + :ash_ai, :ash_state_machine, :ash_neo4j, :ash_jason, diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..65bd0ad --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,73 @@ + + +# Agents working in this repo + +Notes for AI assistants (Claude Code, Cursor, Continue, etc.) and humans pairing with them. + +## Keep `tools do` aligned with `define`d actions + +Each Ash domain in this repo (`DiffoExample.Access`, `DiffoExample.Nbn`) declares two parallel lists: + +1. **`resources do ... resource X do define :name, action: :foo end end`** — the code-interface surface, generating `MyDomain.name/...` functions for Elixir callers. +2. **`tools do tool :name, X, :foo end`** — the MCP surface, exposing the same actions to AI agents via [`ash_ai`](https://hexdocs.pm/ash_ai). + +**When you add a new action to a resource, add it to BOTH places.** Forgetting to add the `tool` entry leaves the action invisible to MCP clients — the test suite won't catch it, the code compiles, the action works for Elixir callers, and the AI silently can't reach it. + +The convention is: + +- One `tool` entry per `define`-d action. +- Tool name matches the code-interface name where possible (e.g. `define :build_cable` ↔ `tool :build_cable, Cable, :build`). Disambiguate with a suffix where two resources have actions of the same name (e.g. `tool :assign_port_on_card` and `tool :assign_port_on_ntd`). +- Read actions exposed as their natural read names (`tool :get_cable_by_id, Cable, :read`). + +## Why the discipline + +Tool-via-existing-action is the WWZD-shaped pattern: authorisation, validation, after-action choreography, multi-tenancy, polymorphism — all inherited from the action without writing AI-specific logic. The AI gets the same access an RSP-actor consumer has, no more and no less. That property only holds if every action the AI should be able to reach is declared as a tool. + +## When NOT to add a tool + +- **Internal-only actions** that no consumer (Elixir, HTTP, MCP) should call directly. These typically don't have a `define` either. +- **Provider primitives** (`Diffo.Provider.create_party`, `Diffo.Provider.create_place`) — deliberately not exposed via Access/Nbn MCP. When Access/Nbn grow their own party/place relationship-management actions, expose those instead, and let authorisation gate what each actor can do. +- **Duplicate `define`s wrapping the same action** — e.g. `define :get_rsp_by_epid, action: :read, get_by: :id` and `define :get_rsp_by_short_name, action: :read, get_by: :short_name` both wrap the `:read` action with different filters. One tool (`tool :get_rsp_by_epid, Rsp, :read`) covers both — the AI can supply the filter shape it needs via tool arguments. The code-interface defines are an Elixir convenience; the tool exposes the underlying action. + +## Where this matters most + +If you're modifying: + +- A `*.ex` file under `lib/access/resources/`, `lib/access/services/`, or `lib/nbn/resources/` that adds/renames a `define` in its domain — also touch `lib/access/access.ex` or `lib/nbn/nbn.ex` and update the `tools do` block. +- The `tools do` block itself — make sure the resource module is aliased at the top of the domain file. + +## Quick check + +After changes, count alignment: + +```bash +# tools declared +grep -c "tool :" lib/access/access.ex lib/nbn/nbn.ex + +# actions code-interface-defined +grep -c "define :" lib/access/access.ex lib/nbn/nbn.ex +``` + +The two should match (modulo intentional exclusions). + +## Before you commit + +Two checks before any commit, every commit: + +```bash +mix format # auto-format every changed file to project style +reuse lint # ensure every file has SPDX-FileCopyrightText + SPDX-License-Identifier +``` + +New `.ex` / `.exs` files start with a comment header matching the existing +files (see `lib/diffo_example/util.ex` for the canonical form). New markdown +files use an HTML-comment variant (see `README.md`). `reuse lint` will tell +you which files are missing copyright/license info; if you've created a +new file and haven't added the header, this is the place to catch it. + +Forgetting either is the easiest way to introduce CI noise the reviewer +has to clean up. Save them both the time. diff --git a/documentation/how_to/setup_mcp.md b/documentation/how_to/setup_mcp.md new file mode 100644 index 0000000..20acaff --- /dev/null +++ b/documentation/how_to/setup_mcp.md @@ -0,0 +1,245 @@ + + +# Setting up the diffo_example MCP server + +This how-to walks through running the diffo_example MCP server locally and +wiring AI clients (Claude Code, Claude Desktop, Cursor, custom) to it. The MCP +surface exposes every action declared in the Access and Nbn domains' `tools do` +blocks as a callable tool — 60 tools across the two domains as of writing. + +See [issue #44](https://github.com/diffo-dev/diffo_example/issues/44) for the +design context, and Zach's +[Ash AI blog post](https://alembic.com.au/blog/ash-ai-comprehensive-llm-toolbox-for-ash-framework) +for the framing. + +## Prerequisites + +- Elixir / Erlang installed (per `mix.exs` — currently `~> 1.18`). +- Neo4j running locally with the credentials configured in `config/dev.exs`. +- Dependencies fetched: `mix deps.get`. +- Initial RSP data seeded on first start (handled automatically by + `DiffoExample.Nbn.Initializer` when the app starts in dev). + +## Starting the server + +In a terminal in the project root: + +```bash +MIX_ENV=dev mix run --no-halt +``` + +This boots the supervision tree, including `Plug.Cowboy` on port 4000. The MCP +server is forwarded from `DiffoExample.Nbn.Router` at the path `/mcp`. The same +Cowboy listener also serves the JSON:API routes and the `/catalog` endpoint. + +You'll see Neo4j connection logs, then the listener-bound message. Leave it +running. + +## Verifying MCP is up + +In a second terminal, send the three canonical MCP requests with `curl`. + +### `initialize` + +```bash +curl -sS -X POST -H "Content-Type: application/json" \ + -d '{ + "jsonrpc":"2.0", + "method":"initialize", + "id":1, + "params":{ + "protocolVersion":"2024-11-05", + "capabilities":{}, + "clientInfo":{"name":"curl","version":"0"} + } + }' \ + http://localhost:4000/mcp +``` + +Expected response shape: + +```json +{ + "id": 1, + "jsonrpc": "2.0", + "result": { + "capabilities": {"tools": {"listChanged": false}}, + "protocolVersion": "2024-11-05", + "serverInfo": {"name": "MCP Server", "version": "0.2.1"} + } +} +``` + +### `tools/list` + +```bash +curl -sS -X POST -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"tools/list","id":2}' \ + http://localhost:4000/mcp +``` + +Returns the full set of tools. Each tool entry includes `name`, `description`, +and `inputSchema` (JSON Schema generated from the underlying Ash action's +arguments). To count and peek at the first few names: + +```bash +curl -sS -X POST -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","method":"tools/list","id":2}' \ + http://localhost:4000/mcp | + python3 -c ' +import json, sys +d = json.load(sys.stdin) +ts = d.get("result", {}).get("tools", []) +print("count:", len(ts)) +for t in ts[:8]: + print("-", t["name"]) +print("..." if len(ts) > 8 else "")' +``` + +### `tools/call` + +Try `list_rsps` — no-arg, returns the seeded RSPs: + +```bash +curl -sS -X POST -H "Content-Type: application/json" \ + -d '{ + "jsonrpc":"2.0", + "method":"tools/call", + "id":3, + "params":{"name":"list_rsps","arguments":{}} + }' \ + http://localhost:4000/mcp +``` + +Should return a `result.content[0].text` containing the JSON-encoded list of +six RSPs (Wedge-tail, Quokka, Ibis, Taipan, Echidna, Dugong). + +## Wiring Claude Code + +In any directory, with the server running: + +```bash +claude mcp add diffo --transport http http://localhost:4000/mcp +``` + +This adds an entry to your global `~/.claude.json`. For a project-scoped +config (writes to a `.mcp.json` next to the cwd): + +```bash +claude mcp add diffo --transport http -s project http://localhost:4000/mcp +``` + +After adding, any Claude Code session can call the tools. Try prompts like: + +- "list the RSPs" +- "qualify a DSL service for a customer with this location and these parties" +- "build a cable, define it with 60 pairs as copper, then auto-assign a pair to + the qualified service" +- "show me the path with id X and its assigned ports and cables" + +Claude will discover the right tools via `tools/list`, call them with the +arguments inferred from the prompt, and explain the results. + +## Wiring Claude Desktop + +Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) +or the equivalent on your OS: + +```json +{ + "mcpServers": { + "diffo": { + "type": "http", + "url": "http://localhost:4000/mcp" + } + } +} +``` + +Restart Claude Desktop. The tools appear under the hammer icon in the prompt +input area. Hover any tool to see its description and schema. + +## Wiring Cursor / Continue / other MCP clients + +Any MCP-aware editor or assistant follows the same shape. Point them at +`http://localhost:4000/mcp` with HTTP transport. Refer to the client's MCP +configuration docs for the exact file/UI path. + +## What tools are available + +The full list is discoverable via `tools/list` (see above). At a glance: + +**Access (~23 tools)** +- DslAccess: read, qualify, qualify_result, design_result +- Shelf: read, build, define, relate, assign_slot +- Card: read, build, define, relate, assign_port (`:assign_port_on_card`) +- Cable: read, build, define, relate, assign_pair +- Path: read, build, define, relate + +**Nbn (~37 tools)** +- NbnEthernet, Uni, Avc, Nni: read, build, define, relate (each) +- Ntd: read, build, define, assign_port (`:assign_port_on_ntd`), relate +- Cvc: read, build, define, assign_cvlan, relate +- NniGroup: read, build, define, assign_svlan, relate +- Rsp: inventory (`:list_rsps`), read, build, activate, suspend, deactivate + +The tool name in MCP matches the `tools do` declaration in +`lib/access/access.ex` and `lib/nbn/nbn.ex`. + +## Adjusting the surface + +The router is wired with `tools: true`, which exposes every tool declared on +every domain in the configured `:diffo_example, :ash_domains` list. To narrow +the surface, edit the `forward "/mcp"` block in `lib/nbn/router.ex` and +replace `tools: true` with an explicit list of tool atoms — e.g. +`tools: [:list_rsps, :get_path_by_id, :qualify_dsl]`. + +For separate scopes (read-only vs full-surface, public vs authenticated), add +a second `forward "/mcp_admin"` with its own tool list. + +## Adding new tools + +When you add a new action to a resource, add it to the appropriate domain's +`tools do` block as well. See [AGENTS.md](../../AGENTS.md) for the +keep-in-alignment convention. The compile won't catch a missing tool entry; +the action will simply be invisible to MCP clients. + +## Authorisation + +The current router is unauthenticated — local-dev use case. Tools execute +with no actor, which means: + +- Resources with `bypass DiffoExample.Nbn.Checks.NoActor do authorize_if always() end` + (every NBN resource — see `lib/nbn/rsp_ownership.ex`) execute as a + bypass — Perentie-internal access. +- Actions without that bypass policy may fail or behave differently with no + actor. + +For multi-tenant or production deployments, add +`AshAuthentication.Strategy.ApiKey.Plug` (or similar) in a pipeline ahead of +the MCP forward, and pass the resolved actor through to the tool calls. The +existing RSP-actor multi-tenancy machinery will then bound what each MCP +client can do based on its principal. + +## Troubleshooting + +- **`tools/list` returns count: 0** — the `forward "/mcp"` block isn't passing + `tools: true` (or a tool list) and `otp_app: :diffo_example`. Check + `lib/nbn/router.ex`. +- **`Connection refused` on `http://localhost:4000/mcp`** — the server isn't + running, or it crashed at startup. Restart with `MIX_ENV=dev mix run --no-halt` + and watch the startup logs. +- **A specific tool call errors with policy/auth message** — the action requires + an actor that the unauthenticated MCP request can't supply. Either run with + `MIX_ENV=test` (some test bypasses), use a tool that doesn't need an actor, + or wire up auth as above. +- **`tools/call` works in curl but Claude can't find the tool** — restart the + Claude client after adding the MCP server. Many MCP clients only refresh + the tools list on session start. +- **Schema mismatches in tool args** — `inputSchema` in `tools/list` is the + source of truth. The Ash action's arguments (and their `public?` and types) + determine the schema; private arguments aren't exposed. diff --git a/lib/access/access.ex b/lib/access/access.ex index 3d33d79..85760a1 100644 --- a/lib/access/access.ex +++ b/lib/access/access.ex @@ -10,7 +10,8 @@ defmodule DiffoExample.Access do """ use Ash.Domain, otp_app: :diffo, - fragments: [Diffo.Provider.DomainFragment] + fragments: [Diffo.Provider.DomainFragment], + extensions: [AshAi] alias DiffoExample.Access.DslAccess alias DiffoExample.Access.Shelf @@ -31,6 +32,36 @@ defmodule DiffoExample.Access do description "An example showing how TMF Services and Resources for a fictional Access domain can be extended from the Provider domain" end + tools do + tool :get_dsl_by_id, DslAccess, :read + tool :qualify_dsl, DslAccess, :qualify + tool :qualify_dsl_result, DslAccess, :qualify_result + tool :design_dsl_result, DslAccess, :design_result + + tool :get_shelf_by_id, Shelf, :read + tool :build_shelf, Shelf, :build + tool :define_shelf, Shelf, :define + tool :relate_shelf, Shelf, :relate + tool :assign_slot, Shelf, :assign_slot + + tool :get_card_by_id, Card, :read + tool :build_card, Card, :build + tool :define_card, Card, :define + tool :relate_card, Card, :relate + tool :assign_port_on_card, Card, :assign_port + + tool :get_cable_by_id, Cable, :read + tool :build_cable, Cable, :build + tool :define_cable, Cable, :define + tool :relate_cable, Cable, :relate + tool :assign_pair, Cable, :assign_pair + + tool :get_path_by_id, Path, :read + tool :build_path, Path, :build + tool :define_path, Path, :define + tool :relate_path, Path, :relate + end + resources do resource DslAccess do define :get_dsl_by_id, action: :read, get_by: :id diff --git a/lib/nbn/nbn.ex b/lib/nbn/nbn.ex index 3ea16c4..b21cafc 100644 --- a/lib/nbn/nbn.ex +++ b/lib/nbn/nbn.ex @@ -15,7 +15,7 @@ defmodule DiffoExample.Nbn do use Ash.Domain, otp_app: :diffo, fragments: [Diffo.Provider.DomainFragment], - extensions: [AshJsonApi.Domain] + extensions: [AshAi, AshJsonApi.Domain] alias DiffoExample.Nbn.NbnEthernet alias DiffoExample.Nbn.Uni @@ -37,6 +37,53 @@ defmodule DiffoExample.Nbn do description "An example showing how TMF Resources for a fictional NBN domain can be extended from the Provider domain" end + tools do + tool :get_nbn_ethernet_by_id, NbnEthernet, :read + tool :build_nbn_ethernet, NbnEthernet, :build + tool :define_nbn_ethernet, NbnEthernet, :define + tool :relate_nbn_ethernet, NbnEthernet, :relate + + tool :get_uni_by_id, Uni, :read + tool :build_uni, Uni, :build + tool :define_uni, Uni, :define + tool :relate_uni, Uni, :relate + + tool :get_avc_by_id, Avc, :read + tool :build_avc, Avc, :build + tool :define_avc, Avc, :define + tool :relate_avc, Avc, :relate + + tool :get_ntd_by_id, Ntd, :read + tool :build_ntd, Ntd, :build + tool :define_ntd, Ntd, :define + tool :assign_port_on_ntd, Ntd, :assign_port + tool :relate_ntd, Ntd, :relate + + tool :get_cvc_by_id, Cvc, :read + tool :build_cvc, Cvc, :build + tool :define_cvc, Cvc, :define + tool :assign_cvlan, Cvc, :assign_cvlan + tool :relate_cvc, Cvc, :relate + + tool :get_nni_group_by_id, NniGroup, :read + tool :build_nni_group, NniGroup, :build + tool :define_nni_group, NniGroup, :define + tool :assign_svlan, NniGroup, :assign_svlan + tool :relate_nni_group, NniGroup, :relate + + tool :get_nni_by_id, Nni, :read + tool :build_nni, Nni, :build + tool :define_nni, Nni, :define + tool :relate_nni, Nni, :relate + + tool :list_rsps, Rsp, :inventory + tool :get_rsp_by_epid, Rsp, :read + tool :create_rsp, Rsp, :build + tool :activate_rsp, Rsp, :activate + tool :suspend_rsp, Rsp, :suspend + tool :deactivate_rsp, Rsp, :deactivate + end + json_api do routes do base_route "/nbnEthernet", NbnEthernet do diff --git a/lib/nbn/router.ex b/lib/nbn/router.ex index c20452c..d72af52 100644 --- a/lib/nbn/router.ex +++ b/lib/nbn/router.ex @@ -31,5 +31,13 @@ defmodule DiffoExample.Nbn.Router do |> send_resp(200, result) end + forward "/mcp", + to: AshAi.Mcp.Router, + init_opts: [ + tools: true, + otp_app: :diffo_example, + protocol_version_statement: "2024-11-05" + ] + forward "/", to: DiffoExample.Nbn.ApiRouter end diff --git a/mix.exs b/mix.exs index 24df66e..0c7bf97 100644 --- a/mix.exs +++ b/mix.exs @@ -63,6 +63,7 @@ defmodule DiffoExample.MixProject do "README.md": [title: "Guide"], "documentation/domains/diffo_example_nbn.livemd": [title: "NBN Livebook"], "documentation/domains/nbn.md": [title: "The NBN Domain"], + "documentation/how_to/setup_mcp.md": [title: "Setup the MCP server"], "LICENSES/MIT.md": [title: "License"] ], groups_for_extras: [ @@ -89,12 +90,13 @@ defmodule DiffoExample.MixProject do defp deps do [ {:diffo, diffo_version("~> 0.4.0")}, + {:ash_ai, "~> 0.6"}, {:ash_json_api, "~> 1.6"}, {:plug_cowboy, "~> 2.7"}, {:picosat_elixir, "~> 0.2.0"}, {:simple_sat, ">= 0.0.0"}, {:usage_rules, "~> 1.0", only: [:dev]}, - {:req, "~> 0.5", only: [:dev, :test]}, + {:req, "~> 0.5"}, {:igniter, "~> 0.6", only: [:dev, :test]}, {:ex_doc, "~> 0.37", only: [:dev, :test], runtime: false} ] diff --git a/mix.lock b/mix.lock index 5be34e7..2643834 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,7 @@ %{ + "abnf_parsec": {:hex, :abnf_parsec, "2.1.0", "c4e88d5d089f1698297c0daced12be1fb404e6e577ecf261313ebba5477941f9", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e0ed6290c7cc7e5020c006d1003520390c9bdd20f7c3f776bd49bfe3c5cd362a"}, "ash": {:hex, :ash, "3.25.2", "d23c52a9f823e98895d0cf1dc8bbf5d22943ffa45ba087e583d94bb05d205b2e", [:mix], [{:crux, ">= 0.1.2 and < 1.0.0-0", [hex: :crux, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 1.0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.6.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.3", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c4e3fb9252719dd3fec84610a5a19e309f298265076da23c0bef21de237e98bb"}, + "ash_ai": {:hex, :ash_ai, "0.6.1", "d30bda859f17bed34302cd3d0c16acf56841c43c2fa3b9271c681122b8297d01", [:mix], [{:ash, ">= 3.7.1 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_authentication, "~> 4.8", [hex: :ash_authentication, repo: "hexpm", optional: true]}, {:ash_json_api, ">= 1.4.27 and < 2.0.0-0", [hex: :ash_json_api, repo: "hexpm", optional: false]}, {:ash_oban, "~> 0.5", [hex: :ash_oban, repo: "hexpm", optional: true]}, {:ash_phoenix, "~> 2.0", [hex: :ash_phoenix, repo: "hexpm", optional: true]}, {:ash_postgres, "~> 2.5", [hex: :ash_postgres, repo: "hexpm", optional: true]}, {:igniter, "~> 0.5", [hex: :igniter, repo: "hexpm", optional: true]}, {:open_api_spex, "~> 3.0", [hex: :open_api_spex, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:plug, "~> 1.17", [hex: :plug, repo: "hexpm", optional: true]}, {:req_llm, "~> 1.7", [hex: :req_llm, repo: "hexpm", optional: false]}], "hexpm", "e950e5743ef093fdd2561cf608a1719d46dcc4125bee6f50d43fd6f8a9526c05"}, "ash_jason": {:hex, :ash_jason, "3.1.0", "84a88dfe5e25a20d55cf2d2664885cd086fa45871e8777aedc3ad96a282e2a6f", [:mix], [{:ash, ">= 3.6.2 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:spark, ">= 2.1.21 and < 3.0.0", [hex: :spark, repo: "hexpm", optional: false]}], "hexpm", "71e6bbc421fb2cf7079f8804814145cca458116c839fc798f9606b806e07eb2b"}, "ash_json_api": {:hex, :ash_json_api, "1.6.5", "ff925107ebdced10407a6045dc3ff9e8335fe3485ce042f899817a2b47f49b5f", [:mix], [{:ash, ">= 3.19.1 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:igniter, ">= 0.3.58 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:json_xema, "~> 0.4", [hex: :json_xema, repo: "hexpm", optional: false]}, {:open_api_spex, "~> 3.16", [hex: :open_api_spex, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: false]}, {:plug, "~> 1.11", [hex: :plug, repo: "hexpm", optional: false]}, {:spark, ">= 2.2.10", [hex: :spark, repo: "hexpm", optional: false]}], "hexpm", "ab2f413d977a560843bbf7a7f6bc486b74e944ef51d9adf93c355a4bf984b0df"}, "ash_neo4j": {:hex, :ash_neo4j, "0.6.0", "8814efcd122d83a6bf6734b2c8ab9119deb9ab5412e267e6f71a4627db9ccf63", [:mix], [{:ash, ">= 3.24.2 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:bolty, ">= 0.0.12", [hex: :bolty, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:spark, ">= 2.7.0", [hex: :spark, repo: "hexpm", optional: false]}, {:usage_rules, "~> 1.2", [hex: :usage_rules, repo: "hexpm", optional: true]}], "hexpm", "2cceba9ce60331fa73b256503484119f7b578c2a87b4bfc0a6c3545ae853ac36"}, @@ -13,21 +15,27 @@ "crux": {:hex, :crux, "0.1.3", "c698dee09d811678dcddad11a02a832c6bff100f1a7aee49ac44c87485bdbac8", [:mix], [{:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: true]}], "hexpm", "04188ea9c1cee13e3ef132417200765857402dcc581f45a8a7862eec3b0530ff"}, "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, "decimal": {:hex, :decimal, "2.4.1", "6c0fbede12fb122ba685e9ab41c6a40c129e322b3aa192f9e072e61f3a6ffaf2", [:mix], [], "hexpm", "7e618897933a8455f19a727d7c5e50a2c071a544b700e5e724298ecb4340187f"}, + "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "diffo": {:hex, :diffo, "0.4.0", "919101d104f3c3c8fbe61ee38f94da84a9a0f107dac94875b00b6cca30b5c04e", [:mix], [{:ash, ">= 3.24.2 and < 4.0.0-0", [hex: :ash, repo: "hexpm", optional: false]}, {:ash_jason, "~> 3.0", [hex: :ash_jason, repo: "hexpm", optional: false]}, {:ash_neo4j, "~> 0.6", [hex: :ash_neo4j, repo: "hexpm", optional: false]}, {:ash_outstanding, "~> 0.2.3", [hex: :ash_outstanding, repo: "hexpm", optional: false]}, {:ash_state_machine, "~> 0.2.12", [hex: :ash_state_machine, repo: "hexpm", optional: false]}, {:bolty, ">= 0.0.12", [hex: :bolty, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:uuid, "~> 1.1", [hex: :uuid, repo: "hexpm", optional: false]}], "hexpm", "6e3b37d523ee1e19c92f21956b9c3f710dc3ed87d5be813d0ed120f331bc630d"}, + "dotenvy": {:hex, :dotenvy, "1.1.1", "00e318f3c51de9fafc4b48598447e386f19204dc18ca69886905bb8f8b08b667", [:mix], [], "hexpm", "c8269471b5701e9e56dc86509c1199ded2b33dce088c3471afcfef7839766d8e"}, "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, "ecto": {:hex, :ecto, "3.13.6", "352135b474f91d1ab99a1b502171d207e9db60421c9e3d0ecab4c7ab96b24d14", [:mix], [{:decimal, "~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8afa059bc16cd2c94739ec0a11e3e5df69d828125119109bef35f20a21a76af2"}, "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"}, "ets": {:hex, :ets, "0.9.0", "79c6a6c205436780486f72d84230c6cba2f8a9920456750ddd1e47389107d5fd", [:mix], [], "hexpm", "2861fdfb04bcaeff370f1a5904eec864f0a56dcfebe5921ea9aadf2a481c822b"}, "ex_ast": {:hex, :ex_ast, "0.12.0", "052ad63711da41b7efbfb3490dbf3d757bb67caec17d02f6deb0db4a0363e5f6", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.7", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "66b4797f157d32f0a63c6da227515f78816c0ac8f621f6d7a2b22108e7b4dd85"}, + "ex_aws_auth": {:hex, :ex_aws_auth, "1.3.1", "3963992d6f7cb251b53573603c3615cec70c3f4d86199fdb865ff440295ef7a4", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: true]}], "hexpm", "025793aa08fa419aabdb652db60edbdb2e12346bd447988a1bb5854c4dd64903"}, "ex_doc": {:hex, :ex_doc, "0.40.2", "f50edec428c4b0a457a167de42414c461122a3585a99515a69d09fff19e5597e", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "4fa426e2beb47854a162e2c488727fdec51cd4692e319b23810c2804cb1a40fe"}, "finch": {:hex, :finch, "0.22.0", "5c48fa6f9706a78eb9036cacb67b8b996b4e66d111c543f4c29bb0f879a6806b", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.8", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b94e83c47780fc6813f746a1f1a34ee65cda42da4c5ea26a68f0acc4498e23dc"}, "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, + "idna": {:hex, :idna, "7.1.0", "1067a13043538129602d2f2ce6899d8713125c7d19734aa557ce2e3ea55bd4f1", [:rebar3], [], "hexpm", "6ae959a025bf36df61a8cab8508d9654891b5426a84c44d82deaffd6ddf8c71f"}, "igniter": {:hex, :igniter, "0.8.0", "c7cab589440e5f20ff68e00f60eb094378114dab3105c0784ce8140f8dfdd2c0", [:mix], [{:ex_ast, "~> 0.5", [hex: :ex_ast, repo: "hexpm", optional: false]}, {:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "fcd99096fde4797f7b48bebddcfc58785569acd696346a3eb385bf813f47a7cc"}, "iterex": {:hex, :iterex, "0.1.2", "58f9b9b9a22a55cbfc7b5234a9c9c63eaac26d276b3db80936c0e1c60355a5a6", [:mix], [], "hexpm", "2e103b8bcc81757a9af121f6dc0df312c9a17220f302b1193ef720460d03029d"}, "jason": {:hex, :jason, "1.4.5", "2e3a008590b0b8d7388c20293e9dcc9cf3e5d642fd2a114e4cbbb52e595d940a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b0c823996102bcd0239b3c2444eb00409b72f6a140c1950bc8b457d836b30684"}, "json_xema": {:hex, :json_xema, "0.6.5", "060459c9c9152650edb4427b1acbc61fa43a23bcea0301d200cafa76e0880f37", [:mix], [{:conv_case, "~> 0.2", [hex: :conv_case, repo: "hexpm", optional: false]}, {:xema, "~> 0.16", [hex: :xema, repo: "hexpm", optional: false]}], "hexpm", "b8ffdbc2f67aa8b91b44e1ba0ab77eb5c0b0142116f8fbb804977fb939d470ef"}, + "jsv": {:hex, :jsv, "0.19.1", "9dd02fb0a7beee58917a1a364cdd125c2df86ff99177d1b0bdd6b896c25d05cf", [:mix], [{:abnf_parsec, "~> 2.0", [hex: :abnf_parsec, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:idna, "~> 6.0 or ~> 7.0", [hex: :idna, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:texture, "~> 1.0", [hex: :texture, repo: "hexpm", optional: false]}], "hexpm", "ccdd8eb4a7953a0bd939951b0924e4a41aaa6b3934b0875b64f3dbcae97b09be"}, "libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"}, + "llm_db": {:hex, :llm_db, "2026.4.8", "402ba69f4281e964e885aa465eebbb72b2f74aadc88c631ffe4a6ddd6a5d62b6", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:dotenvy, "~> 1.1", [hex: :dotenvy, repo: "hexpm", optional: false]}, {:igniter, "~> 0.7", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:toml, "~> 0.7", [hex: :toml, repo: "hexpm", optional: false]}, {:zoi, "~> 0.10", [hex: :zoi, repo: "hexpm", optional: false]}], "hexpm", "d34d1ff9931572e74873a3d7e6334efed1f0fe8e9a85e2c283c02b8970cee525"}, "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, "makeup_erlang": {:hex, :makeup_erlang, "1.1.0", "835f7e60792e08824cda445639555d7bf1bbbddb1b60b306e33cb6f6db24dc74", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "1cd6780fb1dd1a03979abaed0fe82712b0625118fd5257d3ebbf73f960c73c3c"}, @@ -37,6 +45,7 @@ "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, + "open_api_spex": {:hex, :open_api_spex, "3.22.3", "0e383bf23cc3a060bffaebbcd09fc06bfc908d948c00e518aed36bbf8a2fe473", [:mix], [{:decimal, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0 or ~> 6.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "5f74f1878fdc38f8e961b0b943ac7af88dcf3a82a0c0ef6680ddfd3d161aecbd"}, "outstanding": {:hex, :outstanding, "0.2.5", "2f40416eb9617748cb1f8ae4c8ed94515d731f9c4fcee4f902355d30bc0792cc", [:mix], [], "hexpm", "bb47a210f0d2804ea6b8477fa6f4d15e8c58c18acee79d8e06c9296e6dd004cd"}, "owl": {:hex, :owl, "0.13.0", "26010e066d5992774268f3163506972ddac0a7e77bfe57fa42a250f24d6b876e", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "59bf9d11ce37a4db98f57cb68fbfd61593bf419ec4ed302852b6683d3d2f7475"}, "phoenix": {:hex, :phoenix, "1.8.7", "d8d755b4ff4b449f610223dd706b4ae64155cb720d3dc09c706c079ecea189e4", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "47352f72d6ab31009ef77516b1b3a14745be97b54061fd458031b9d8294869d5"}, @@ -49,7 +58,9 @@ "ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"}, "reactor": {:hex, :reactor, "1.0.2", "79e4e81d016ab0016afd10bb4c18cb3a574f08f10f8e53be5f08ce27f8eed541", [:mix], [{:igniter, "~> 0.4", [hex: :igniter, repo: "hexpm", optional: true]}, {:iterex, "~> 0.1", [hex: :iterex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:multigraph, "~> 0.16.1-mg.2", [hex: :multigraph, repo: "hexpm", optional: false]}, {:spark, ">= 2.3.3 and < 3.0.0-0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.2", [hex: :splode, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.2", [hex: :telemetry, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.11", [hex: :yaml_elixir, repo: "hexpm", optional: false]}, {:ymlr, "~> 5.0", [hex: :ymlr, repo: "hexpm", optional: false]}], "hexpm", "19fd55aaaadaae28f55133351051c25d4ac217f99e3e5a67940cc4a321e3948e"}, "req": {:hex, :req, "0.5.18", "48e6431cb4135e8a7815e745177485369a9b4a9924d5fe68ca00eb09ceaed1ef", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.21.0 or ~> 0.22.0", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "fa03812c440a9754bf34355e0c5d4f3ed316458db62e3284b7a352ef8dc0b996"}, + "req_llm": {:hex, :req_llm, "1.11.0", "56c823e40e1409ef7f8d972301058277671161262ca9201db8c5b531a33f25ae", [:mix], [{:dotenvy, "~> 1.1", [hex: :dotenvy, repo: "hexpm", optional: false]}, {:ex_aws_auth, "~> 1.3", [hex: :ex_aws_auth, repo: "hexpm", optional: false]}, {:igniter, "~> 0.7", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:jsv, "~> 0.11", [hex: :jsv, repo: "hexpm", optional: false]}, {:llm_db, "~> 2026.4.0", [hex: :llm_db, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.1", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:server_sent_events, "~> 1.0.0", [hex: :server_sent_events, repo: "hexpm", optional: false]}, {:splode, "~> 0.3.0", [hex: :splode, repo: "hexpm", optional: false]}, {:uniq, "~> 0.6", [hex: :uniq, repo: "hexpm", optional: false]}, {:websockex, "~> 0.5.1", [hex: :websockex, repo: "hexpm", optional: false]}, {:zoi, "~> 0.14", [hex: :zoi, repo: "hexpm", optional: false]}], "hexpm", "81118f324632e60d7641911eb97d01798129fb480d04a860ba6007507b866671"}, "rewrite": {:hex, :rewrite, "1.3.0", "67448ba7975690b35ba7e7f35717efcce317dbd5963cb0577aa7325c1923121a", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "d111ac7ff3a58a802ef4f193bbd1831e00a9c57b33276e5068e8390a212714a5"}, + "server_sent_events": {:hex, :server_sent_events, "1.0.0", "e82089ac6b93ebd3c0562fd728492bbe4b5140678ffc891abfa8cce717c2c1ff", [:mix], [], "hexpm", "7899caea3e27850549f671fc9e6c53d55a8e6a78474f6b9623820aae6bb41ec7"}, "simple_sat": {:hex, :simple_sat, "0.1.4", "39baf72cdca14f93c0b6ce2b6418b72bbb67da98fa9ca4384e2f79bbc299899d", [:mix], [], "hexpm", "3569b68e346a5fd7154b8d14173ff8bcc829f2eb7b088c30c3f42a383443930b"}, "sourceror": {:hex, :sourceror, "1.12.0", "da354c5f35aad3cc1132f5d5b0d8437d865e2661c263260480bab51b5eedb437", [:mix], [], "hexpm", "755703683bd014ebcd5de9acc24b68fb874a660a568d1d63f8f98cd8a6ef9cd0"}, "spark": {:hex, :spark, "2.7.0", "e685b33c038f12851993880bb7e3b326117612eb746fe15828678c152f8321c6", [:mix], [{:igniter, ">= 0.3.64 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:sourceror, "~> 1.2", [hex: :sourceror, repo: "hexpm", optional: true]}], "hexpm", "e2f675fbda32375b01d9ee7c652671531027fd043bf4a91bafdb2ab716aa1122"}, @@ -58,12 +69,17 @@ "stream_data": {:hex, :stream_data, "1.3.0", "bde37905530aff386dea1ddd86ecbf00e6642dc074ceffc10b7d4e41dfd6aac9", [:mix], [], "hexpm", "3cc552e286e817dca43c98044c706eec9318083a1480c52ae2688b08e2936e3c"}, "telemetry": {:hex, :telemetry, "1.4.2", "a0cb522801dffb1c49fe6e30561badffc7b6d0e180db1300df759faa22062855", [:rebar3], [], "hexpm", "928f6495066506077862c0d1646609eed891a4326bee3126ba54b60af61febb1"}, "text_diff": {:hex, :text_diff, "0.1.0", "1caf3175e11a53a9a139bc9339bd607c47b9e376b073d4571c031913317fecaa", [:mix], [], "hexpm", "d1ffaaecab338e49357b6daa82e435f877e0649041ace7755583a0ea3362dbd7"}, + "texture": {:hex, :texture, "1.0.0", "8791d167516749da9a3e5542af2fff49ba14474768b4af1b735dd46850461a22", [:mix], [{:abnf_parsec, "~> 2.0", [hex: :abnf_parsec, repo: "hexpm", optional: false]}], "hexpm", "77d3ca19d884f5263655b74b63b55f2952d21326fa324dcd74ab87a435427c10"}, + "toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"}, + "uniq": {:hex, :uniq, "0.6.3", "68acff834cce1817b52928ef346662735c5413a4fec9c3b0d4a9126de5b2b489", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "2b2a900d0a20f3a55d3de0bc8150495e4a71255734dfb23889991bda5aca6c7d"}, "usage_rules": {:hex, :usage_rules, "1.2.6", "a7b3f8d6e5d265701139d5714749c37c54bb82230a4c51ec54a12a1e4769b9d1", [:mix], [{:igniter, ">= 0.6.6 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}], "hexpm", "608411b9876a16a9d62a427dbaf42faf458e4cd0a508b3bd7e5ee71502073582"}, "uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm", "c790593b4c3b601f5dc2378baae7efaf5b3d73c4c6456ba85759905be792f2ac"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, "websock_adapter": {:hex, :websock_adapter, "0.5.9", "43dc3ba6d89ef5dec5b1d0a39698436a1e856d000d84bf31a3149862b01a287f", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "5534d5c9adad3c18a0f58a9371220d75a803bf0b9a3d87e6fe072faaeed76a08"}, + "websockex": {:hex, :websockex, "0.5.1", "9de28d37bbe34f371eb46e29b79c94c94fff79f93c960d842fbf447253558eb4", [:mix], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8ef39576ed56bc3804c9cd8626f8b5d6b5721848d2726c0ccd4f05385a3c9f14"}, "xema": {:hex, :xema, "0.17.7", "7eeda174b70a5f7fb1cc2e9fa3a7d4e78e206a99866c107d477309410b678cf2", [:mix], [{:conv_case, "~> 0.2.2", [hex: :conv_case, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "7e3d7c0629282c21af6aaa5e2ba593218cd764a57bd1ae49e2c4412324e904cd"}, "yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"}, "yaml_elixir": {:hex, :yaml_elixir, "2.12.1", "d74f2d82294651b58dac849c45a82aaea639766797359baff834b64439f6b3f4", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "d9ac16563c737d55f9bfeed7627489156b91268a3a21cd55c54eb2e335207fed"}, "ymlr": {:hex, :ymlr, "5.1.5", "0b9207c7940be3f2bc29b77cd55109d5aa2f4dcde6575942017335769e6f5628", [:mix], [], "hexpm", "7030cb240c46850caeb3b01be745307632be319b15f03083136f6251f49b516d"}, + "zoi": {:hex, :zoi, "0.18.4", "849c1ccdf69a4a7b7b6c2e41766312bcc4edf1e0af5bfb9f2f3d98234191b8ef", [:mix], [{:decimal, "~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "587fb221824ae7343fca3af90b8a4c53ac5cf9019891cf3aba215b43be2ba05d"}, }