Current section
Files
Jump to
Current section
Files
lib/codex/plugins.ex
defmodule Codex.Plugins do
@moduledoc """
Local plugin authoring helpers for manifests, marketplaces, and scaffold flows.
This namespace is intentionally separate from the app-server runtime APIs on
`Codex.AppServer`. Use `Codex.Plugins.*` to author files locally with normal
Elixir file IO. Use `Codex.AppServer.plugin_*` only when you want runtime
discovery, read, install, or uninstall verification against a running Codex
app-server.
"""
alias Codex.Plugins.{Manifest, Marketplace, Reader, Scaffold, Writer}
@type scaffold_result :: %{
scope: Codex.Plugins.Paths.scope(),
plugin_name: String.t(),
plugin_root: String.t(),
manifest_path: String.t(),
manifest: Manifest.t(),
skill_paths: [String.t()],
marketplace_path: String.t() | nil,
marketplace: Marketplace.t() | nil,
created_paths: [String.t()]
}
@type scaffold_error ::
{:plugin_io, %{action: atom(), path: String.t(), reason: term()}}
| {:invalid_plugin_json, %{path: String.t(), reason: String.t(), raw_reason: term()}}
| {:plugin_file_exists, %{path: String.t()}}
| {:repo_root_not_found, %{cwd: String.t()}}
| {:invalid_plugin_scope, %{scope: term()}}
| {:invalid_plugin_name, %{name: term(), message: String.t()}}
| {:invalid_plugin_root, %{path: String.t(), message: String.t()}}
| {:invalid_marketplace_path, %{path: String.t(), message: String.t()}}
| {:invalid_marketplace_source_path,
%{path: String.t(), source_path: String.t(), message: String.t()}}
| {:plugin_conflict, %{path: String.t(), plugin_name: String.t()}}
| {:invalid_plugin_manifest, CliSubprocessCore.Schema.error_detail()}
| {:invalid_plugin_marketplace, CliSubprocessCore.Schema.error_detail()}
| {:invalid_plugin_marketplace_plugin, CliSubprocessCore.Schema.error_detail()}
@doc """
Builds and validates a manifest struct.
"""
@spec new_manifest(map() | keyword() | Manifest.t()) ::
{:ok, Manifest.t()} | {:error, term()}
def new_manifest(attrs), do: Manifest.parse(attrs)
@doc """
Validates manifest data and returns normalized issues on failure.
"""
@spec validate_manifest(map() | keyword() | Manifest.t()) :: :ok | {:error, [map()]}
def validate_manifest(attrs) do
attrs
|> Manifest.parse()
|> to_validation_result()
end
@doc """
Builds and validates a marketplace struct.
"""
@spec new_marketplace(map() | keyword() | Marketplace.t()) ::
{:ok, Marketplace.t()} | {:error, term()}
def new_marketplace(attrs), do: Marketplace.parse(attrs)
@doc """
Validates marketplace data and returns normalized issues on failure.
"""
@spec validate_marketplace(map() | keyword() | Marketplace.t()) :: :ok | {:error, [map()]}
def validate_marketplace(attrs) do
attrs
|> Marketplace.parse()
|> to_validation_result()
end
@doc """
Reads and validates a local plugin manifest.
"""
@spec read_manifest(Path.t()) :: {:ok, Manifest.t()} | {:error, term()}
def read_manifest(path), do: Reader.read_manifest(path)
@doc """
Reads and validates a local marketplace file.
"""
@spec read_marketplace(Path.t()) :: {:ok, Marketplace.t()} | {:error, term()}
def read_marketplace(path), do: Reader.read_marketplace(path)
@doc """
Writes a manifest deterministically using local file IO.
"""
@spec write_manifest(Path.t(), map() | keyword() | Manifest.t(), keyword()) ::
:ok | {:error, term()}
def write_manifest(path, manifest, opts \\ []) do
with {:ok, manifest} <- Manifest.parse(manifest) do
Writer.write_manifest(path, manifest, opts)
end
end
@doc """
Writes a marketplace deterministically using local file IO.
"""
@spec write_marketplace(Path.t(), map() | keyword() | Marketplace.t(), keyword()) ::
:ok | {:error, term()}
def write_marketplace(path, marketplace, opts \\ []) do
with {:ok, marketplace} <- Marketplace.parse(marketplace) do
Writer.write_marketplace(path, marketplace, opts)
end
end
@doc """
Safely appends or replaces one marketplace entry without erasing unrelated entries.
"""
@spec add_marketplace_plugin(Path.t(), map() | keyword(), keyword()) ::
{:ok, map()} | {:error, term()}
def add_marketplace_plugin(path, plugin, opts \\ []) do
with {:ok, plugin} <- Marketplace.parse_plugin(plugin) do
Writer.add_marketplace_plugin(path, plugin, opts)
end
end
@doc """
Scaffolds a minimal local plugin tree.
"""
@spec scaffold(keyword()) :: {:ok, scaffold_result()} | {:error, scaffold_error()}
def scaffold(opts), do: Scaffold.scaffold(opts)
defp to_validation_result({:ok, _struct}), do: :ok
defp to_validation_result({:error, {_tag, details}}), do: {:error, details.issues}
end