Current section
Files
Jump to
Current section
Files
lib/mix/tasks/annotai.install.ex
if Code.ensure_loaded?(Igniter) do
defmodule Mix.Tasks.Annotai.Install do
@shortdoc "Installs Annotai into your Phoenix app"
@moduledoc false
@plug_example """
+ if Mix.env() == :dev do
+ plug Annotai
+ end
if code_reloading? do
socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
plug Phoenix.LiveReloader
plug Phoenix.CodeReloader
end
"""
use Igniter.Mix.Task
@impl Igniter.Mix.Task
def info(_argv, _composing_task) do
%Igniter.Mix.Task.Info{
group: :annotai,
example: "mix annotai.install"
}
end
@impl Igniter.Mix.Task
def igniter(igniter) do
igniter
|> setup_endpoint()
|> enable_debug_config()
|> registration_notice()
end
# 1. Add the dev-only plug to the endpoint, before the code-reloading block.
defp setup_endpoint(igniter) do
{igniter, endpoint} =
Igniter.Libs.Phoenix.select_endpoint(igniter, nil, "Which endpoint should serve Annotai?")
if endpoint do
add_plug_to_endpoint(igniter, endpoint)
else
Igniter.add_warning(igniter, """
No endpoint found or selected for Annotai. Add the plug manually, for example:
#{@plug_example}
""")
end
end
defp add_plug_to_endpoint(igniter, endpoint) do
Igniter.Project.Module.find_and_update_module!(igniter, endpoint, fn zipper ->
with :error <-
Igniter.Code.Common.move_to(zipper, fn zipper ->
Igniter.Code.Function.function_call?(zipper, :plug) and
Igniter.Code.Function.argument_equals?(zipper, 0, Annotai)
end),
{:ok, zipper} <- Igniter.Code.Common.move_to(zipper, &code_reloading?/1) do
{:ok,
Igniter.Code.Common.add_code(
zipper,
"""
if Mix.env() == :dev do
plug Annotai
end
""",
placement: :before
)}
else
{:ok, _} ->
{:ok, zipper}
:error ->
{:warning,
"""
Could not find the code-reloading section of your endpoint `#{inspect(endpoint)}`.
We look for `if code_reloading? do`. Add the plug manually, for example:
#{@plug_example}
"""}
end
end)
end
# 2. Enable the source-mapping config in dev (the killer feature needs these).
defp enable_debug_config(igniter) do
igniter
|> Igniter.Project.Config.configure(
"dev.exs",
:phoenix_live_view,
[:debug_heex_annotations],
true
)
|> Igniter.Project.Config.configure(
"dev.exs",
:phoenix_live_view,
[:debug_attributes],
true
)
end
# 3. Tell the developer how to register the MCP server (named per app, so multiple
# apps don't collide).
defp registration_notice(igniter) do
app = Igniter.Project.Application.app_name(igniter)
Igniter.add_notice(igniter, """
Annotai installed. Start your app (`mix phx.server`) and the widget appears bottom-right.
To let your AI agent read annotations, register Annotai's MCP server — replace 4000 with
your dev server's port:
claude mcp add --transport http annotai_#{app} http://localhost:4000/annotai/mcp
""")
end
defp code_reloading?(zipper) do
Igniter.Code.Function.function_call?(zipper, :if, 2) &&
Igniter.Code.Function.argument_matches_predicate?(
zipper,
0,
&Igniter.Code.Common.variable?(&1, :code_reloading?)
)
end
end
else
defmodule Mix.Tasks.Annotai.Install do
@shortdoc "Installs Annotai into your Phoenix app (requires igniter)"
@moduledoc false
use Mix.Task
def run(_argv) do
Mix.shell().error("""
The task `annotai.install` requires igniter. Add it and try again:
{:igniter, "~> 0.8", only: [:dev, :test]}
Or install Annotai manually:
# lib/my_app_web/endpoint.ex — before the `if code_reloading? do` block
if Mix.env() == :dev do
plug Annotai
end
# config/dev.exs
config :phoenix_live_view, debug_heex_annotations: true, debug_attributes: true
To control it via an environment variable, gate it behind one:
# config/dev.exs
config :annotai, enabled: System.get_env("DISABLE_ANNOTAI") != "true"
Then register the MCP server with your agent:
claude mcp add --transport http annotai_my_app http://localhost:4000/annotai/mcp
""")
exit({:shutdown, 1})
end
end
end