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