use codex_config::HooksFile; /// Parsed plugin metadata parameterized by its resource locator representation. /// /// Host loading uses absolute paths, while resolved packages replace them with /// authority-bound locators before exposing the manifest to consumers. #[derive(Debug, Clone, PartialEq, Eq)] pub struct PluginManifest { pub name: String, pub version: Option, pub description: Option, pub keywords: Vec, pub paths: PluginManifestPaths, pub interface: Option>, } /// Component resources declared by a plugin manifest. #[derive(Debug, Clone, PartialEq, Eq)] pub struct PluginManifestPaths { pub skills: Option, pub mcp_servers: Option, pub apps: Option, pub hooks: Option>, } /// Hook declarations embedded in or referenced by a plugin manifest. #[derive(Debug, Clone, PartialEq, Eq)] pub enum PluginManifestHooks { Paths(Vec), Inline(Vec), } /// Optional model- and UI-facing plugin metadata. #[derive(Debug, Clone, PartialEq, Eq)] pub struct PluginManifestInterface { pub display_name: Option, pub short_description: Option, pub long_description: Option, pub developer_name: Option, pub category: Option, pub capabilities: Vec, pub website_url: Option, pub privacy_policy_url: Option, pub terms_of_service_url: Option, pub default_prompt: Option>, pub brand_color: Option, pub composer_icon: Option, pub logo: Option, pub screenshots: Vec, } impl Default for PluginManifestInterface { fn default() -> Self { Self { display_name: None, short_description: None, long_description: None, developer_name: None, category: None, capabilities: Vec::new(), website_url: None, privacy_policy_url: None, terms_of_service_url: None, default_prompt: None, brand_color: None, composer_icon: None, logo: None, screenshots: Vec::new(), } } } impl PluginManifest { pub(crate) fn try_map_resources( self, mut map: impl FnMut(Resource) -> Result, ) -> Result, Error> { let PluginManifest { name, version, description, keywords, paths, interface, } = self; let PluginManifestPaths { skills, mcp_servers, apps, hooks, } = paths; let hooks = match hooks { Some(PluginManifestHooks::Paths(paths)) => Some(PluginManifestHooks::Paths( paths .into_iter() .map(&mut map) .collect::, _>>()?, )), Some(PluginManifestHooks::Inline(hooks)) => Some(PluginManifestHooks::Inline(hooks)), None => None, }; let interface = match interface { Some(interface) => { let PluginManifestInterface { display_name, short_description, long_description, developer_name, category, capabilities, website_url, privacy_policy_url, terms_of_service_url, default_prompt, brand_color, composer_icon, logo, screenshots, } = interface; Some(PluginManifestInterface { display_name, short_description, long_description, developer_name, category, capabilities, website_url, privacy_policy_url, terms_of_service_url, default_prompt, brand_color, composer_icon: composer_icon.map(&mut map).transpose()?, logo: logo.map(&mut map).transpose()?, screenshots: screenshots .into_iter() .map(&mut map) .collect::, _>>()?, }) } None => None, }; Ok(PluginManifest { name, version, description, keywords, paths: PluginManifestPaths { skills: skills.map(&mut map).transpose()?, mcp_servers: mcp_servers.map(&mut map).transpose()?, apps: apps.map(&mut map).transpose()?, hooks, }, interface, }) } }