Packages
Multi-surface application runtime for Elixir. One TEA module renders to terminal, browser (LiveView), SSH, and MCP (agents). 30+ widgets, flexbox + CSS grid, AI agent runtime, distributed swarm with CRDTs, time-travel debugging, session recording, sandboxed REPL, and agentic commerce.
Current section
Files
Jump to
Current section
Files
lib/raxol/terminal/plugin/manager.ex
defmodule Raxol.Terminal.Plugin.Manager do
@moduledoc """
Manages terminal plugins with advanced features:
- Plugin loading and unloading
- Plugin lifecycle management
- Plugin API and hooks
- Plugin configuration and state management
"""
@type plugin :: %{
name: String.t(),
version: String.t(),
description: String.t(),
author: String.t(),
hooks: [String.t()],
config: map(),
state: map()
}
@type hook :: %{
name: String.t(),
callback: function(),
priority: integer()
}
@type t :: %__MODULE__{
plugins: %{String.t() => plugin()},
hooks: %{String.t() => [hook()]},
config: map(),
metrics: %{
plugin_loads: integer(),
plugin_unloads: integer(),
hook_calls: integer(),
config_updates: integer()
}
}
defstruct [
:plugins,
:hooks,
:config,
:metrics
]
@doc """
Creates a new plugin manager with the given options.
"""
@spec new(keyword()) :: t()
def new(opts \\ []) do
%__MODULE__{
plugins: %{},
hooks: %{},
config: Map.new(opts),
metrics: %{
plugin_loads: 0,
plugin_unloads: 0,
hook_calls: 0,
config_updates: 0
}
}
end
@doc """
Loads a plugin into the manager.
"""
@spec load_plugin(t(), plugin()) :: {:ok, t()} | {:error, term()}
def load_plugin(manager, plugin) do
with :ok <- validate_plugin(plugin),
:ok <- check_plugin_conflicts(manager, plugin) do
updated_plugins = Map.put(manager.plugins, plugin.name, plugin)
updated_hooks = register_plugin_hooks(manager.hooks, plugin)
updated_manager = %{
manager
| plugins: updated_plugins,
hooks: updated_hooks,
metrics: update_metrics(manager.metrics, :plugin_loads)
}
{:ok, updated_manager}
else
{:error, reason} -> {:error, reason}
end
end
@doc """
Unloads a plugin from the manager.
"""
@spec unload_plugin(t(), String.t()) :: {:ok, t()} | {:error, term()}
def unload_plugin(manager, plugin_name) do
case Map.get(manager.plugins, plugin_name) do
nil ->
{:error, :plugin_not_found}
plugin ->
updated_plugins = Map.delete(manager.plugins, plugin_name)
updated_hooks = unregister_plugin_hooks(manager.hooks, plugin)
updated_manager = %{
manager
| plugins: updated_plugins,
hooks: updated_hooks,
metrics: update_metrics(manager.metrics, :plugin_unloads)
}
{:ok, updated_manager}
end
end
@doc """
Calls a hook with the given arguments.
"""
@spec call_hook(t(), String.t(), [term()]) ::
{:ok, [term()], t()} | {:error, term()}
def call_hook(manager, hook_name, args \\ []) do
case Map.get(manager.hooks, hook_name) do
nil ->
{:error, :hook_not_found}
hooks ->
results =
Enum.map(hooks, fn hook ->
apply_hook(hook, args)
end)
updated_manager = %{
manager
| metrics: update_metrics(manager.metrics, :hook_calls)
}
{:ok, results, updated_manager}
end
end
@doc """
Updates the configuration for a plugin.
"""
@spec update_plugin_config(t(), String.t(), map()) ::
{:ok, t()} | {:error, term()}
def update_plugin_config(manager, plugin_name, config) do
case Map.get(manager.plugins, plugin_name) do
nil ->
{:error, :plugin_not_found}
plugin ->
updated_plugin = %{plugin | config: Map.merge(plugin.config, config)}
updated_plugins = Map.put(manager.plugins, plugin_name, updated_plugin)
updated_manager = %{
manager
| plugins: updated_plugins,
metrics: update_metrics(manager.metrics, :config_updates)
}
{:ok, updated_manager}
end
end
@doc """
Gets the current plugin metrics.
"""
@spec get_metrics(t()) :: map()
def get_metrics(manager) do
manager.metrics
end
# Private helper functions
defp validate_plugin(plugin) do
required_fields = [
:name,
:version,
:description,
:author,
:hooks,
:config,
:state
]
if Enum.all?(required_fields, &Map.has_key?(plugin, &1)) do
:ok
else
{:error, :invalid_plugin}
end
end
defp check_plugin_conflicts(manager, plugin) do
if Map.has_key?(manager.plugins, plugin.name) do
{:error, :plugin_already_loaded}
else
:ok
end
end
defp register_plugin_hooks(hooks, plugin) do
Enum.reduce(plugin.hooks, hooks, fn hook_name, acc ->
hook = %{
name: hook_name,
callback: &apply_hook/2,
priority: 0
}
Map.update(acc, hook_name, [hook], &[hook | &1])
end)
end
defp unregister_plugin_hooks(hooks, plugin) do
Enum.reduce(plugin.hooks, hooks, fn hook_name, acc ->
Map.update(
acc,
hook_name,
[],
&Enum.reject(&1, fn h -> h.name == plugin.name end)
)
end)
end
defp apply_hook(hook, args) do
try do
hook.callback.(args)
rescue
e -> {:error, {:hook_error, e}}
end
end
defp update_metrics(metrics, :plugin_loads) do
update_in(metrics.plugin_loads, &(&1 + 1))
end
defp update_metrics(metrics, :plugin_unloads) do
update_in(metrics.plugin_unloads, &(&1 + 1))
end
defp update_metrics(metrics, :hook_calls) do
update_in(metrics.hook_calls, &(&1 + 1))
end
defp update_metrics(metrics, :config_updates) do
update_in(metrics.config_updates, &(&1 + 1))
end
end