1#[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 PluginListCommand {
27 json: bool,
28 available: bool,
29}
30
31impl PluginListCommand {
32 #[must_use]
34 pub fn new() -> Self {
35 Self::default()
36 }
37
38 #[must_use]
40 pub fn json(mut self) -> Self {
41 self.json = true;
42 self
43 }
44
45 #[must_use]
47 pub fn available(mut self) -> Self {
48 self.available = true;
49 self
50 }
51}
52
53impl ClaudeCommand for PluginListCommand {
54 type Output = CommandOutput;
55
56 fn args(&self) -> Vec<String> {
57 let mut args = vec!["plugin".to_string(), "list".to_string()];
58 if self.json {
59 args.push("--json".to_string());
60 }
61 if self.available {
62 args.push("--available".to_string());
63 }
64 args
65 }
66
67 #[cfg(feature = "async")]
68 async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
69 exec::run_claude(claude, self.args()).await
70 }
71}
72
73#[derive(Debug, Clone)]
90pub struct PluginInstallCommand {
91 plugin: String,
92 scope: Option<Scope>,
93}
94
95impl PluginInstallCommand {
96 #[must_use]
98 pub fn new(plugin: impl Into<String>) -> Self {
99 Self {
100 plugin: plugin.into(),
101 scope: None,
102 }
103 }
104
105 #[must_use]
107 pub fn scope(mut self, scope: Scope) -> Self {
108 self.scope = Some(scope);
109 self
110 }
111}
112
113impl ClaudeCommand for PluginInstallCommand {
114 type Output = CommandOutput;
115
116 fn args(&self) -> Vec<String> {
117 let mut args = vec!["plugin".to_string(), "install".to_string()];
118 if let Some(ref scope) = self.scope {
119 args.push("--scope".to_string());
120 args.push(scope.as_arg().to_string());
121 }
122 args.push(self.plugin.clone());
123 args
124 }
125
126 #[cfg(feature = "async")]
127 async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
128 exec::run_claude(claude, self.args()).await
129 }
130}
131
132#[derive(Debug, Clone)]
134pub struct PluginUninstallCommand {
135 plugin: String,
136 scope: Option<Scope>,
137}
138
139impl PluginUninstallCommand {
140 #[must_use]
142 pub fn new(plugin: impl Into<String>) -> Self {
143 Self {
144 plugin: plugin.into(),
145 scope: None,
146 }
147 }
148
149 #[must_use]
151 pub fn scope(mut self, scope: Scope) -> Self {
152 self.scope = Some(scope);
153 self
154 }
155}
156
157impl ClaudeCommand for PluginUninstallCommand {
158 type Output = CommandOutput;
159
160 fn args(&self) -> Vec<String> {
161 let mut args = vec!["plugin".to_string(), "uninstall".to_string()];
162 if let Some(ref scope) = self.scope {
163 args.push("--scope".to_string());
164 args.push(scope.as_arg().to_string());
165 }
166 args.push(self.plugin.clone());
167 args
168 }
169
170 #[cfg(feature = "async")]
171 async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
172 exec::run_claude(claude, self.args()).await
173 }
174}
175
176#[derive(Debug, Clone)]
178pub struct PluginEnableCommand {
179 plugin: String,
180 scope: Option<Scope>,
181}
182
183impl PluginEnableCommand {
184 #[must_use]
186 pub fn new(plugin: impl Into<String>) -> Self {
187 Self {
188 plugin: plugin.into(),
189 scope: None,
190 }
191 }
192
193 #[must_use]
195 pub fn scope(mut self, scope: Scope) -> Self {
196 self.scope = Some(scope);
197 self
198 }
199}
200
201impl ClaudeCommand for PluginEnableCommand {
202 type Output = CommandOutput;
203
204 fn args(&self) -> Vec<String> {
205 let mut args = vec!["plugin".to_string(), "enable".to_string()];
206 if let Some(ref scope) = self.scope {
207 args.push("--scope".to_string());
208 args.push(scope.as_arg().to_string());
209 }
210 args.push(self.plugin.clone());
211 args
212 }
213
214 #[cfg(feature = "async")]
215 async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
216 exec::run_claude(claude, self.args()).await
217 }
218}
219
220#[derive(Debug, Clone)]
222pub struct PluginDisableCommand {
223 plugin: Option<String>,
224 scope: Option<Scope>,
225 all: bool,
226}
227
228impl PluginDisableCommand {
229 #[must_use]
231 pub fn new(plugin: impl Into<String>) -> Self {
232 Self {
233 plugin: Some(plugin.into()),
234 scope: None,
235 all: false,
236 }
237 }
238
239 #[must_use]
241 pub fn all() -> Self {
242 Self {
243 plugin: None,
244 scope: None,
245 all: true,
246 }
247 }
248
249 #[must_use]
251 pub fn scope(mut self, scope: Scope) -> Self {
252 self.scope = Some(scope);
253 self
254 }
255}
256
257impl ClaudeCommand for PluginDisableCommand {
258 type Output = CommandOutput;
259
260 fn args(&self) -> Vec<String> {
261 let mut args = vec!["plugin".to_string(), "disable".to_string()];
262 if self.all {
263 args.push("--all".to_string());
264 }
265 if let Some(ref scope) = self.scope {
266 args.push("--scope".to_string());
267 args.push(scope.as_arg().to_string());
268 }
269 if let Some(ref plugin) = self.plugin {
270 args.push(plugin.clone());
271 }
272 args
273 }
274
275 #[cfg(feature = "async")]
276 async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
277 exec::run_claude(claude, self.args()).await
278 }
279}
280
281#[derive(Debug, Clone)]
283pub struct PluginUpdateCommand {
284 plugin: String,
285 scope: Option<Scope>,
286}
287
288impl PluginUpdateCommand {
289 #[must_use]
291 pub fn new(plugin: impl Into<String>) -> Self {
292 Self {
293 plugin: plugin.into(),
294 scope: None,
295 }
296 }
297
298 #[must_use]
300 pub fn scope(mut self, scope: Scope) -> Self {
301 self.scope = Some(scope);
302 self
303 }
304}
305
306impl ClaudeCommand for PluginUpdateCommand {
307 type Output = CommandOutput;
308
309 fn args(&self) -> Vec<String> {
310 let mut args = vec!["plugin".to_string(), "update".to_string()];
311 if let Some(ref scope) = self.scope {
312 args.push("--scope".to_string());
313 args.push(scope.as_arg().to_string());
314 }
315 args.push(self.plugin.clone());
316 args
317 }
318
319 #[cfg(feature = "async")]
320 async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
321 exec::run_claude(claude, self.args()).await
322 }
323}
324
325#[derive(Debug, Clone)]
327pub struct PluginValidateCommand {
328 path: String,
329}
330
331impl PluginValidateCommand {
332 #[must_use]
334 pub fn new(path: impl Into<String>) -> Self {
335 Self { path: path.into() }
336 }
337}
338
339impl ClaudeCommand for PluginValidateCommand {
340 type Output = CommandOutput;
341
342 fn args(&self) -> Vec<String> {
343 vec![
344 "plugin".to_string(),
345 "validate".to_string(),
346 self.path.clone(),
347 ]
348 }
349
350 #[cfg(feature = "async")]
351 async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
352 exec::run_claude(claude, self.args()).await
353 }
354}
355
356#[derive(Debug, Clone, Default)]
381pub struct PluginTagCommand {
382 path: Option<String>,
383 dry_run: bool,
384 force: bool,
385 message: Option<String>,
386 push: bool,
387 remote: Option<String>,
388}
389
390impl PluginTagCommand {
391 #[must_use]
394 pub fn new() -> Self {
395 Self::default()
396 }
397
398 #[must_use]
400 pub fn path(mut self, path: impl Into<String>) -> Self {
401 self.path = Some(path.into());
402 self
403 }
404
405 #[must_use]
407 pub fn dry_run(mut self) -> Self {
408 self.dry_run = true;
409 self
410 }
411
412 #[must_use]
414 pub fn force(mut self) -> Self {
415 self.force = true;
416 self
417 }
418
419 #[must_use]
421 pub fn message(mut self, msg: impl Into<String>) -> Self {
422 self.message = Some(msg.into());
423 self
424 }
425
426 #[must_use]
428 pub fn push(mut self) -> Self {
429 self.push = true;
430 self
431 }
432
433 #[must_use]
435 pub fn remote(mut self, remote: impl Into<String>) -> Self {
436 self.remote = Some(remote.into());
437 self
438 }
439}
440
441impl ClaudeCommand for PluginTagCommand {
442 type Output = CommandOutput;
443
444 fn args(&self) -> Vec<String> {
445 let mut args = vec!["plugin".to_string(), "tag".to_string()];
446 if self.dry_run {
447 args.push("--dry-run".to_string());
448 }
449 if self.force {
450 args.push("--force".to_string());
451 }
452 if let Some(ref msg) = self.message {
453 args.push("--message".to_string());
454 args.push(msg.clone());
455 }
456 if self.push {
457 args.push("--push".to_string());
458 }
459 if let Some(ref remote) = self.remote {
460 args.push("--remote".to_string());
461 args.push(remote.clone());
462 }
463 if let Some(ref path) = self.path {
464 args.push(path.clone());
465 }
466 args
467 }
468
469 #[cfg(feature = "async")]
470 async fn execute(&self, claude: &Claude) -> Result<CommandOutput> {
471 exec::run_claude(claude, self.args()).await
472 }
473}
474
475#[cfg(test)]
476mod tests {
477 use super::*;
478 use crate::command::ClaudeCommand;
479
480 #[test]
481 fn test_plugin_list() {
482 let cmd = PluginListCommand::new().json().available();
483 assert_eq!(
484 ClaudeCommand::args(&cmd),
485 vec!["plugin", "list", "--json", "--available"]
486 );
487 }
488
489 #[test]
490 fn test_plugin_install() {
491 let cmd = PluginInstallCommand::new("my-plugin").scope(Scope::User);
492 assert_eq!(
493 ClaudeCommand::args(&cmd),
494 vec!["plugin", "install", "--scope", "user", "my-plugin"]
495 );
496 }
497
498 #[test]
499 fn test_plugin_uninstall() {
500 let cmd = PluginUninstallCommand::new("old-plugin");
501 assert_eq!(
502 ClaudeCommand::args(&cmd),
503 vec!["plugin", "uninstall", "old-plugin"]
504 );
505 }
506
507 #[test]
508 fn test_plugin_enable() {
509 let cmd = PluginEnableCommand::new("my-plugin").scope(Scope::Project);
510 assert_eq!(
511 ClaudeCommand::args(&cmd),
512 vec!["plugin", "enable", "--scope", "project", "my-plugin"]
513 );
514 }
515
516 #[test]
517 fn test_plugin_disable_specific() {
518 let cmd = PluginDisableCommand::new("my-plugin");
519 assert_eq!(
520 ClaudeCommand::args(&cmd),
521 vec!["plugin", "disable", "my-plugin"]
522 );
523 }
524
525 #[test]
526 fn test_plugin_disable_all() {
527 let cmd = PluginDisableCommand::all();
528 assert_eq!(
529 ClaudeCommand::args(&cmd),
530 vec!["plugin", "disable", "--all"]
531 );
532 }
533
534 #[test]
535 fn test_plugin_update() {
536 let cmd = PluginUpdateCommand::new("my-plugin").scope(Scope::Local);
537 assert_eq!(
538 ClaudeCommand::args(&cmd),
539 vec!["plugin", "update", "--scope", "local", "my-plugin"]
540 );
541 }
542
543 #[test]
544 fn test_plugin_validate() {
545 let cmd = PluginValidateCommand::new("/path/to/manifest");
546 assert_eq!(
547 ClaudeCommand::args(&cmd),
548 vec!["plugin", "validate", "/path/to/manifest"]
549 );
550 }
551
552 #[test]
553 fn plugin_tag_defaults_to_just_subcommand() {
554 let cmd = PluginTagCommand::new();
555 assert_eq!(ClaudeCommand::args(&cmd), vec!["plugin", "tag"]);
556 }
557
558 #[test]
559 fn plugin_tag_with_all_options() {
560 let cmd = PluginTagCommand::new()
561 .path("./plugin")
562 .dry_run()
563 .force()
564 .message("release %s")
565 .push()
566 .remote("upstream");
567 assert_eq!(
568 ClaudeCommand::args(&cmd),
569 vec![
570 "plugin",
571 "tag",
572 "--dry-run",
573 "--force",
574 "--message",
575 "release %s",
576 "--push",
577 "--remote",
578 "upstream",
579 "./plugin",
580 ]
581 );
582 }
583}