1use codex_config::HooksFile;
2
3#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct PluginManifest<Resource> {
9 pub name: String,
10 pub version: Option<String>,
11 pub description: Option<String>,
12 pub keywords: Vec<String>,
13 pub paths: PluginManifestPaths<Resource>,
14 pub interface: Option<PluginManifestInterface<Resource>>,
15}
16
17#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct PluginManifestPaths<Resource> {
20 pub skills: Vec<Resource>,
21 pub mcp_servers: Option<PluginManifestMcpServers<Resource>>,
22 pub apps: Option<Resource>,
23 pub hooks: Option<PluginManifestHooks<Resource>>,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq)]
28pub enum PluginManifestMcpServers<Resource> {
29 Path(Resource),
30 Object(String),
31}
32
33#[derive(Debug, Clone, PartialEq, Eq)]
35pub enum PluginManifestHooks<Resource> {
36 Paths(Vec<Resource>),
37 Inline(Vec<HooksFile>),
38}
39
40#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct PluginManifestInterface<Resource> {
43 pub display_name: Option<String>,
44 pub short_description: Option<String>,
45 pub long_description: Option<String>,
46 pub developer_name: Option<String>,
47 pub category: Option<String>,
48 pub capabilities: Vec<String>,
49 pub website_url: Option<String>,
50 pub privacy_policy_url: Option<String>,
51 pub terms_of_service_url: Option<String>,
52 pub default_prompt: Option<Vec<String>>,
53 pub brand_color: Option<String>,
54 pub composer_icon: Option<Resource>,
55 pub logo: Option<Resource>,
56 pub logo_dark: Option<Resource>,
57 pub screenshots: Vec<Resource>,
58}
59
60impl<Resource> Default for PluginManifestInterface<Resource> {
61 fn default() -> Self {
62 Self {
63 display_name: None,
64 short_description: None,
65 long_description: None,
66 developer_name: None,
67 category: None,
68 capabilities: Vec::new(),
69 website_url: None,
70 privacy_policy_url: None,
71 terms_of_service_url: None,
72 default_prompt: None,
73 brand_color: None,
74 composer_icon: None,
75 logo: None,
76 logo_dark: None,
77 screenshots: Vec::new(),
78 }
79 }
80}
81
82impl<Resource> PluginManifest<Resource> {
83 pub fn display_name(&self) -> &str {
85 self.interface
86 .as_ref()
87 .and_then(|interface| interface.display_name.as_deref())
88 .map(str::trim)
89 .filter(|display_name| !display_name.is_empty())
90 .unwrap_or(&self.name)
91 }
92
93 pub fn try_map_resources<Mapped, Error>(
95 self,
96 mut map: impl FnMut(Resource) -> Result<Mapped, Error>,
97 ) -> Result<PluginManifest<Mapped>, Error> {
98 let PluginManifest {
99 name,
100 version,
101 description,
102 keywords,
103 paths,
104 interface,
105 } = self;
106 let PluginManifestPaths {
107 skills,
108 mcp_servers,
109 apps,
110 hooks,
111 } = paths;
112 let hooks = match hooks {
113 Some(PluginManifestHooks::Paths(paths)) => Some(PluginManifestHooks::Paths(
114 paths
115 .into_iter()
116 .map(&mut map)
117 .collect::<Result<Vec<_>, _>>()?,
118 )),
119 Some(PluginManifestHooks::Inline(hooks)) => Some(PluginManifestHooks::Inline(hooks)),
120 None => None,
121 };
122 let mcp_servers = match mcp_servers {
123 Some(PluginManifestMcpServers::Path(path)) => {
124 Some(PluginManifestMcpServers::Path(map(path)?))
125 }
126 Some(PluginManifestMcpServers::Object(servers)) => {
127 Some(PluginManifestMcpServers::Object(servers))
128 }
129 None => None,
130 };
131 let interface = match interface {
132 Some(interface) => {
133 let PluginManifestInterface {
134 display_name,
135 short_description,
136 long_description,
137 developer_name,
138 category,
139 capabilities,
140 website_url,
141 privacy_policy_url,
142 terms_of_service_url,
143 default_prompt,
144 brand_color,
145 composer_icon,
146 logo,
147 logo_dark,
148 screenshots,
149 } = interface;
150 Some(PluginManifestInterface {
151 display_name,
152 short_description,
153 long_description,
154 developer_name,
155 category,
156 capabilities,
157 website_url,
158 privacy_policy_url,
159 terms_of_service_url,
160 default_prompt,
161 brand_color,
162 composer_icon: composer_icon.map(&mut map).transpose()?,
163 logo: logo.map(&mut map).transpose()?,
164 logo_dark: logo_dark.map(&mut map).transpose()?,
165 screenshots: screenshots
166 .into_iter()
167 .map(&mut map)
168 .collect::<Result<Vec<_>, _>>()?,
169 })
170 }
171 None => None,
172 };
173
174 Ok(PluginManifest {
175 name,
176 version,
177 description,
178 keywords,
179 paths: PluginManifestPaths {
180 skills: skills
181 .into_iter()
182 .map(&mut map)
183 .collect::<Result<Vec<_>, _>>()?,
184 mcp_servers,
185 apps: apps.map(&mut map).transpose()?,
186 hooks,
187 },
188 interface,
189 })
190 }
191}