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