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
raxol lib raxol plugins marketplace_client.ex
Raw

lib/raxol/plugins/marketplace_client.ex

defmodule Raxol.Plugins.MarketplaceClient do
@moduledoc """
Plugin Marketplace client for Plugin System v2.0.
Features:
- Plugin discovery and search
- Version management and updates
- Security verification and signatures
- Dependency resolution integration
- User reviews and ratings
- Installation and uninstallation
- License compliance checking
"""
use GenServer
require Logger
alias Raxol.Plugins.{DependencyResolverV2, PluginSandbox}
@type plugin_id :: String.t()
@type version :: String.t()
@type search_filters :: %{
category: String.t() | nil,
author: String.t() | nil,
rating_min: float() | nil,
license: String.t() | nil,
tags: [String.t()] | nil,
compatibility: String.t() | nil
}
@type marketplace_plugin :: %{
id: plugin_id(),
name: String.t(),
version: version(),
description: String.t(),
author: String.t(),
license: String.t(),
category: String.t(),
tags: [String.t()],
rating: float(),
downloads: non_neg_integer(),
repository: String.t(),
documentation: String.t(),
screenshots: [String.t()],
dependencies: [String.t()],
api_compatibility: String.t(),
trust_level: :trusted | :verified | :community | :unverified,
signature: String.t() | nil,
checksum: String.t(),
size_bytes: non_neg_integer(),
published_at: DateTime.t(),
updated_at: DateTime.t()
}
defstruct [
marketplace_url: nil,
api_key: nil,
cache: %{},
installed_plugins: %{},
update_notifications: [],
security_policies: %{},
download_cache_dir: nil
]
# Marketplace API
@doc """
Starts the marketplace client with configuration.
"""
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
@doc """
Searches for plugins in the marketplace.
"""
def search_plugins(query, filters \\ %{}) do
GenServer.call(__MODULE__, {:search_plugins, query, filters})
end
@doc """
Gets detailed information about a specific plugin.
"""
def get_plugin_info(plugin_id, version \\ "latest") do
GenServer.call(__MODULE__, {:get_plugin_info, plugin_id, version})
end
@doc """
Lists all available versions of a plugin.
"""
def list_plugin_versions(plugin_id) do
GenServer.call(__MODULE__, {:list_plugin_versions, plugin_id})
end
@doc """
Downloads and installs a plugin from the marketplace.
"""
def install_plugin(plugin_id, version \\ "latest", opts \\ %{}) do
GenServer.call(__MODULE__, {:install_plugin, plugin_id, version, opts}, 60_000)
end
@doc """
Uninstalls a plugin and cleans up dependencies.
"""
def uninstall_plugin(plugin_id) do
GenServer.call(__MODULE__, {:uninstall_plugin, plugin_id})
end
@doc """
Updates a plugin to the latest version.
"""
def update_plugin(plugin_id, opts \\ %{}) do
GenServer.call(__MODULE__, {:update_plugin, plugin_id, opts}, 60_000)
end
@doc """
Checks for available updates for installed plugins.
"""
def check_for_updates do
GenServer.call(__MODULE__, :check_for_updates)
end
@doc """
Lists installed plugins with their marketplace status.
"""
def list_installed_plugins do
GenServer.call(__MODULE__, :list_installed_plugins)
end
@doc """
Verifies plugin signature and security.
"""
def verify_plugin_security(plugin_id, version) do
GenServer.call(__MODULE__, {:verify_plugin_security, plugin_id, version})
end
@doc """
Gets user reviews and ratings for a plugin.
"""
def get_plugin_reviews(plugin_id) do
GenServer.call(__MODULE__, {:get_plugin_reviews, plugin_id})
end
@doc """
Submits a review for a plugin (requires authentication).
"""
def submit_plugin_review(plugin_id, rating, review_text) do
GenServer.call(__MODULE__, {:submit_plugin_review, plugin_id, rating, review_text})
end
# GenServer Implementation
@impl GenServer
def init(opts) do
state = %__MODULE__{
marketplace_url: Keyword.get(opts, :marketplace_url, default_marketplace_url()),
api_key: Keyword.get(opts, :api_key),
cache: %{},
installed_plugins: load_installed_plugins(),
update_notifications: [],
security_policies: load_security_policies(),
download_cache_dir: Keyword.get(opts, :cache_dir, default_cache_dir())
}
# Schedule periodic update checks
schedule_update_check()
Logger.info("[MarketplaceClient] Initialized with marketplace: #{state.marketplace_url}")
{:ok, state}
end
@impl GenServer
def handle_call({:search_plugins, query, filters}, _from, state) do
case search_plugins_impl(query, filters, state) do
{:ok, results} ->
{:reply, {:ok, results}, state}
{:error, reason} ->
{:reply, {:error, reason}, state}
end
end
def handle_call({:get_plugin_info, plugin_id, version}, _from, state) do
case get_plugin_info_impl(plugin_id, version, state) do
{:ok, plugin_info} ->
# Update cache
cache_key = {plugin_id, version}
updated_cache = Map.put(state.cache, cache_key, {plugin_info, DateTime.utc_now()})
{:reply, {:ok, plugin_info}, %{state | cache: updated_cache}}
{:error, reason} ->
{:reply, {:error, reason}, state}
end
end
def handle_call({:list_plugin_versions, plugin_id}, _from, state) do
case list_plugin_versions_impl(plugin_id, state) do
{:ok, versions} ->
{:reply, {:ok, versions}, state}
{:error, reason} ->
{:reply, {:error, reason}, state}
end
end
def handle_call({:install_plugin, plugin_id, version, opts}, _from, state) do
case install_plugin_impl(plugin_id, version, opts, state) do
{:ok, updated_state} ->
Logger.info("[MarketplaceClient] Successfully installed #{plugin_id}@#{version}")
{:reply, :ok, updated_state}
{:error, reason} ->
Logger.error("[MarketplaceClient] Failed to install #{plugin_id}@#{version}: #{inspect(reason)}")
{:reply, {:error, reason}, state}
end
end
def handle_call({:uninstall_plugin, plugin_id}, _from, state) do
case uninstall_plugin_impl(plugin_id, state) do
{:ok, updated_state} ->
Logger.info("[MarketplaceClient] Successfully uninstalled #{plugin_id}")
{:reply, :ok, updated_state}
{:error, reason} ->
{:reply, {:error, reason}, state}
end
end
def handle_call({:update_plugin, plugin_id, opts}, _from, state) do
case update_plugin_impl(plugin_id, opts, state) do
{:ok, updated_state} ->
{:reply, :ok, updated_state}
{:error, reason} ->
{:reply, {:error, reason}, state}
end
end
def handle_call(:check_for_updates, _from, state) do
case check_for_updates_impl(state) do
{:ok, updates, updated_state} ->
{:reply, {:ok, updates}, updated_state}
{:error, reason} ->
{:reply, {:error, reason}, state}
end
end
def handle_call(:list_installed_plugins, _from, state) do
installed_with_status = add_marketplace_status(state.installed_plugins, state)
{:reply, {:ok, installed_with_status}, state}
end
def handle_call({:verify_plugin_security, plugin_id, version}, _from, state) do
case verify_plugin_security_impl(plugin_id, version, state) do
{:ok, verification_result} ->
{:reply, {:ok, verification_result}, state}
{:error, reason} ->
{:reply, {:error, reason}, state}
end
end
def handle_call({:get_plugin_reviews, plugin_id}, _from, state) do
case get_plugin_reviews_impl(plugin_id, state) do
{:ok, reviews} ->
{:reply, {:ok, reviews}, state}
{:error, reason} ->
{:reply, {:error, reason}, state}
end
end
def handle_call({:submit_plugin_review, plugin_id, rating, review_text}, _from, state) do
case submit_plugin_review_impl(plugin_id, rating, review_text, state) do
{:ok, review} ->
{:reply, {:ok, review}, state}
{:error, reason} ->
{:reply, {:error, reason}, state}
end
end
@impl GenServer
def handle_info(:check_updates, state) do
case check_for_updates_impl(state) do
{:ok, updates, updated_state} ->
if length(updates) > 0 do
Logger.info("[MarketplaceClient] Found #{length(updates)} plugin updates available")
end
schedule_update_check()
{:noreply, updated_state}
{:error, reason} ->
Logger.error("[MarketplaceClient] Failed to check for updates: #{inspect(reason)}")
schedule_update_check()
{:noreply, state}
end
end
# Private Implementation
defp search_plugins_impl(query, filters, _state) do
# Mock implementation - would make HTTP request to marketplace
Logger.debug("[MarketplaceClient] Searching for plugins: #{query}")
# Simulate marketplace response
mock_results = [
%{
id: "terminal-themes",
name: "Terminal Themes",
version: "2.1.0",
description: "Beautiful color themes for your terminal",
author: "theme-creator",
license: "MIT",
category: "Appearance",
tags: ["themes", "colors", "ui"],
rating: 4.8,
downloads: 12543,
trust_level: :verified
},
%{
id: "git-integration",
name: "Git Integration",
version: "1.5.4",
description: "Seamless Git integration with status display",
author: "git-dev",
license: "Apache-2.0",
category: "Development",
tags: ["git", "vcs", "development"],
rating: 4.6,
downloads: 8921,
trust_level: :trusted
}
]
filtered_results = apply_search_filters(mock_results, filters)
{:ok, filtered_results}
end
defp get_plugin_info_impl(plugin_id, version, state) do
# Check cache first
cache_key = {plugin_id, version}
case Map.get(state.cache, cache_key) do
{cached_info, cached_at} ->
# Check if cache is still valid (1 hour)
if DateTime.diff(DateTime.utc_now(), cached_at, :second) < 3600 do
{:ok, cached_info}
else
fetch_plugin_info_from_marketplace(plugin_id, version, state)
end
nil ->
fetch_plugin_info_from_marketplace(plugin_id, version, state)
end
end
defp fetch_plugin_info_from_marketplace(plugin_id, version, _state) do
# Mock implementation - would make HTTP request
Logger.debug("[MarketplaceClient] Fetching plugin info: #{plugin_id}@#{version}")
case plugin_id do
"terminal-themes" ->
{:ok, %{
id: plugin_id,
name: "Terminal Themes",
version: version,
description: "Beautiful color themes for your terminal emulator",
author: "theme-creator",
license: "MIT",
category: "Appearance",
tags: ["themes", "colors", "ui", "customization"],
rating: 4.8,
downloads: 12543,
repository: "https://github.com/theme-creator/terminal-themes",
documentation: "https://terminal-themes.dev/docs",
screenshots: ["https://cdn.marketplace.com/screenshots/themes-1.png"],
dependencies: [],
api_compatibility: "^1.0.0",
trust_level: :verified,
signature: "signature-hash-here",
checksum: "sha256:abcd1234...",
size_bytes: 2048576,
published_at: ~U[2023-01-15 10:30:00Z],
updated_at: ~U[2023-09-20 14:45:00Z]
}}
_other ->
{:error, :plugin_not_found}
end
end
defp install_plugin_impl(plugin_id, version, opts, state) do
# 1. Get plugin info
case get_plugin_info_impl(plugin_id, version, state) do
{:ok, plugin_info} ->
# 2. Verify security
case verify_plugin_security_impl(plugin_id, version, state) do
{:ok, security_result} ->
if security_result.safe do
# 3. Resolve dependencies
case resolve_plugin_dependencies(plugin_info) do
{:ok, dependencies} ->
# 4. Download and install
case download_and_install_plugin(plugin_info, dependencies, opts, state) do
{:ok, updated_state} ->
{:ok, updated_state}
{:error, reason} ->
{:error, {:installation_failed, reason}}
end
{:error, reason} ->
{:error, {:dependency_resolution_failed, reason}}
end
else
{:error, {:security_verification_failed, security_result.issues}}
end
{:error, reason} ->
{:error, {:security_check_failed, reason}}
end
{:error, reason} ->
{:error, reason}
end
end
defp download_and_install_plugin(plugin_info, dependencies, _opts, state) do
# Mock implementation - would download and install plugin
Logger.info("[MarketplaceClient] Installing #{plugin_info.name} with #{length(dependencies)} dependencies")
# Simulate successful installation
installation_info = %{
plugin_id: plugin_info.id,
version: plugin_info.version,
installed_at: DateTime.utc_now(),
installation_path: "/path/to/plugins/#{plugin_info.id}",
dependencies: dependencies
}
updated_installed = Map.put(state.installed_plugins, plugin_info.id, installation_info)
{:ok, %{state | installed_plugins: updated_installed}}
end
defp resolve_plugin_dependencies(plugin_info) do
# Use dependency resolver to resolve plugin dependencies
case DependencyResolverV2.resolve_dependencies(plugin_info, %{}) do
{:ok, resolved} -> {:ok, resolved}
error -> error
end
end
defp verify_plugin_security_impl(plugin_id, version, _state) do
# Mock implementation - would verify signatures and check security
Logger.debug("[MarketplaceClient] Verifying security for #{plugin_id}@#{version}")
security_result = %{
safe: true,
trust_level: :verified,
signature_valid: true,
checksum_valid: true,
issues: [],
scanned_at: DateTime.utc_now()
}
{:ok, security_result}
end
defp check_for_updates_impl(state) do
# Check each installed plugin for updates
updates = Enum.reduce(state.installed_plugins, [], fn {plugin_id, install_info}, acc ->
case check_plugin_update(plugin_id, install_info.version, state) do
{:ok, nil} -> acc # No update available
{:ok, update_info} -> [update_info | acc]
{:error, _reason} -> acc # Skip errors
end
end)
# Update notification list
updated_notifications = updates ++ state.update_notifications
updated_state = %{state | update_notifications: updated_notifications}
{:ok, updates, updated_state}
end
defp check_plugin_update(plugin_id, current_version, state) do
case list_plugin_versions_impl(plugin_id, state) do
{:ok, versions} ->
latest_version = Enum.max_by(versions, &Version.parse!/1, Version)
if Version.compare(latest_version, current_version) == :gt do
{:ok, %{
plugin_id: plugin_id,
current_version: current_version,
latest_version: latest_version,
update_available: true
}}
else
{:ok, nil}
end
error ->
error
end
end
# Helper Functions
defp default_marketplace_url do
"https://plugins.raxol.io/api/v1"
end
defp default_cache_dir do
Path.join(System.tmp_dir!(), "raxol_plugin_cache")
end
defp load_installed_plugins do
# Mock implementation - would load from filesystem
%{}
end
defp load_security_policies do
# Load security policies for different trust levels
%{
trusted: PluginSandbox.trusted_policy(),
verified: PluginSandbox.sandboxed_policy(),
community: PluginSandbox.sandboxed_policy(),
unverified: PluginSandbox.untrusted_policy()
}
end
defp schedule_update_check do
# Check for updates every 24 hours
Process.send_after(self(), :check_updates, 24 * 60 * 60 * 1000)
end
defp apply_search_filters(results, filters) do
results
|> filter_by_category(filters[:category])
|> filter_by_author(filters[:author])
|> filter_by_rating(filters[:rating_min])
|> filter_by_license(filters[:license])
|> filter_by_tags(filters[:tags])
end
defp filter_by_category(results, nil), do: results
defp filter_by_category(results, category) do
Enum.filter(results, fn plugin -> plugin.category == category end)
end
defp filter_by_author(results, nil), do: results
defp filter_by_author(results, author) do
Enum.filter(results, fn plugin -> plugin.author == author end)
end
defp filter_by_rating(results, nil), do: results
defp filter_by_rating(results, min_rating) do
Enum.filter(results, fn plugin -> plugin.rating >= min_rating end)
end
defp filter_by_license(results, nil), do: results
defp filter_by_license(results, license) do
Enum.filter(results, fn plugin -> plugin.license == license end)
end
defp filter_by_tags(results, nil), do: results
defp filter_by_tags(results, tags) do
Enum.filter(results, fn plugin ->
Enum.any?(tags, fn tag -> tag in plugin.tags end)
end)
end
defp list_plugin_versions_impl(plugin_id, _state) do
# Mock implementation - would fetch versions from marketplace
case plugin_id do
"terminal-themes" ->
{:ok, ["1.0.0", "1.1.0", "2.0.0", "2.1.0"]}
"git-integration" ->
{:ok, ["1.0.0", "1.2.0", "1.5.0", "1.5.2"]}
_other ->
{:error, :plugin_not_found}
end
end
defp uninstall_plugin_impl(plugin_id, state) do
case Map.get(state.installed_plugins, plugin_id) do
nil ->
{:error, :plugin_not_installed}
_install_info ->
# Remove plugin and clean up dependencies
Logger.info("[MarketplaceClient] Uninstalling #{plugin_id}")
updated_installed = Map.delete(state.installed_plugins, plugin_id)
{:ok, %{state | installed_plugins: updated_installed}}
end
end
defp update_plugin_impl(plugin_id, opts, state) do
case Map.get(state.installed_plugins, plugin_id) do
nil ->
{:error, :plugin_not_installed}
install_info ->
# Check for updates and install if available
case check_plugin_update(plugin_id, install_info.version, state) do
{:ok, nil} ->
{:error, :no_update_available}
{:ok, update_info} ->
install_plugin_impl(plugin_id, update_info.latest_version, opts, state)
error ->
error
end
end
end
defp add_marketplace_status(installed_plugins, _state) do
# Add marketplace status to installed plugins
Map.new(installed_plugins, fn {plugin_id, install_info} ->
enhanced_info = Map.merge(install_info, %{
marketplace_status: :available,
update_available: false, # Would check actual status
trust_level: :verified
})
{plugin_id, enhanced_info}
end)
end
defp get_plugin_reviews_impl(_plugin_id, _state) do
# Mock implementation - would fetch reviews from marketplace
reviews = [
%{
rating: 5,
title: "Excellent plugin!",
review: "Works perfectly and easy to configure.",
author: "user123",
verified_purchase: true,
created_at: ~U[2023-09-15 08:30:00Z]
}
]
{:ok, reviews}
end
defp submit_plugin_review_impl(_plugin_id, _rating, _review_text, _state) do
# Mock implementation - would submit review to marketplace
{:ok, %{
status: :submitted,
message: "Review submitted successfully"
}}
end
end