claude_wrapper/command/
marketplace.rs1#[cfg(feature = "async")]
2use crate::Claude;
3use crate::command::ClaudeCommand;
4#[cfg(feature = "async")]
5use crate::error::Result;
6#[cfg(feature = "async")]
7use crate::exec;
8use crate::exec::CommandOutput;
9use crate::types::Scope;
10
11#[derive(Debug, Clone, Default)]
26pub struct MarketplaceListCommand {
27 json: bool,
28}
29
30impl MarketplaceListCommand {
31 #[must_use]
33 pub fn new() -> Self {
34 Self::default()
35 }
36
37 #[must_use]
39 pub fn json(mut self) -> Self {
40 self.json = true;
41 self
42 }
43}
44
45impl ClaudeCommand for MarketplaceListCommand {
46 type Output = CommandOutput;
47
48 fn args(&self) -> Vec<String> {
49 let mut args = vec![
50 "plugin".to_string(),
51 "marketplace".to_string(),
52 "list".to_string(),
53 ];
54 if self.json {
55 args.push("--json".to_string());
56 }
57 args
58 }
59
60 #[cfg(feature = "async")]
61 async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
62 exec::run_claude(claude, self.args()).await
63 }
64}
65
66#[derive(Debug, Clone)]
83pub struct MarketplaceAddCommand {
84 source: String,
85 scope: Option<Scope>,
86 sparse: Vec<String>,
87}
88
89impl MarketplaceAddCommand {
90 #[must_use]
92 pub fn new(source: impl Into<String>) -> Self {
93 Self {
94 source: source.into(),
95 scope: None,
96 sparse: Vec::new(),
97 }
98 }
99
100 #[must_use]
102 pub fn scope(mut self, scope: Scope) -> Self {
103 self.scope = Some(scope);
104 self
105 }
106
107 #[must_use]
109 pub fn sparse(mut self, paths: impl IntoIterator<Item = impl Into<String>>) -> Self {
110 self.sparse.extend(paths.into_iter().map(Into::into));
111 self
112 }
113}
114
115impl ClaudeCommand for MarketplaceAddCommand {
116 type Output = CommandOutput;
117
118 fn args(&self) -> Vec<String> {
119 let mut args = vec![
120 "plugin".to_string(),
121 "marketplace".to_string(),
122 "add".to_string(),
123 ];
124 if let Some(ref scope) = self.scope {
125 args.push("--scope".to_string());
126 args.push(scope.as_arg().to_string());
127 }
128 if !self.sparse.is_empty() {
129 args.push("--sparse".to_string());
130 args.extend(self.sparse.clone());
131 }
132 args.push(self.source.clone());
133 args
134 }
135
136 #[cfg(feature = "async")]
137 async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
138 exec::run_claude(claude, self.args()).await
139 }
140}
141
142#[derive(Debug, Clone)]
144pub struct MarketplaceRemoveCommand {
145 name: String,
146}
147
148impl MarketplaceRemoveCommand {
149 #[must_use]
151 pub fn new(name: impl Into<String>) -> Self {
152 Self { name: name.into() }
153 }
154}
155
156impl ClaudeCommand for MarketplaceRemoveCommand {
157 type Output = CommandOutput;
158
159 fn args(&self) -> Vec<String> {
160 vec![
161 "plugin".to_string(),
162 "marketplace".to_string(),
163 "remove".to_string(),
164 self.name.clone(),
165 ]
166 }
167
168 #[cfg(feature = "async")]
169 async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
170 exec::run_claude(claude, self.args()).await
171 }
172}
173
174#[derive(Debug, Clone, Default)]
176pub struct MarketplaceUpdateCommand {
177 name: Option<String>,
178}
179
180impl MarketplaceUpdateCommand {
181 #[must_use]
183 pub fn all() -> Self {
184 Self { name: None }
185 }
186
187 #[must_use]
189 pub fn new(name: impl Into<String>) -> Self {
190 Self {
191 name: Some(name.into()),
192 }
193 }
194}
195
196impl ClaudeCommand for MarketplaceUpdateCommand {
197 type Output = CommandOutput;
198
199 fn args(&self) -> Vec<String> {
200 let mut args = vec![
201 "plugin".to_string(),
202 "marketplace".to_string(),
203 "update".to_string(),
204 ];
205 if let Some(ref name) = self.name {
206 args.push(name.clone());
207 }
208 args
209 }
210
211 #[cfg(feature = "async")]
212 async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
213 exec::run_claude(claude, self.args()).await
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220 use crate::command::ClaudeCommand;
221
222 #[test]
223 fn test_marketplace_list() {
224 let cmd = MarketplaceListCommand::new().json();
225 assert_eq!(
226 ClaudeCommand::args(&cmd),
227 vec!["plugin", "marketplace", "list", "--json"]
228 );
229 }
230
231 #[test]
232 fn test_marketplace_add() {
233 let cmd = MarketplaceAddCommand::new("https://github.com/org/mp").scope(Scope::User);
234 assert_eq!(
235 ClaudeCommand::args(&cmd),
236 vec![
237 "plugin",
238 "marketplace",
239 "add",
240 "--scope",
241 "user",
242 "https://github.com/org/mp"
243 ]
244 );
245 }
246
247 #[test]
248 fn test_marketplace_add_sparse() {
249 let cmd = MarketplaceAddCommand::new("https://github.com/org/monorepo")
250 .sparse([".claude-plugin", "plugins"]);
251 let args = ClaudeCommand::args(&cmd);
252 assert!(args.contains(&"--sparse".to_string()));
253 assert!(args.contains(&".claude-plugin".to_string()));
254 assert!(args.contains(&"plugins".to_string()));
255 }
256
257 #[test]
258 fn test_marketplace_remove() {
259 let cmd = MarketplaceRemoveCommand::new("old-mp");
260 assert_eq!(
261 ClaudeCommand::args(&cmd),
262 vec!["plugin", "marketplace", "remove", "old-mp"]
263 );
264 }
265
266 #[test]
267 fn test_marketplace_update_all() {
268 let cmd = MarketplaceUpdateCommand::all();
269 assert_eq!(
270 ClaudeCommand::args(&cmd),
271 vec!["plugin", "marketplace", "update"]
272 );
273 }
274
275 #[test]
276 fn test_marketplace_update_specific() {
277 let cmd = MarketplaceUpdateCommand::new("my-mp");
278 assert_eq!(
279 ClaudeCommand::args(&cmd),
280 vec!["plugin", "marketplace", "update", "my-mp"]
281 );
282 }
283}