git_worktree_manager/operations/setup_claude/
claude_cli.rs1use std::path::Path;
7use std::process::Command;
8
9#[derive(Debug)]
10pub enum ClaudeCliError {
11 NotInstalled,
13 NonZeroExit { code: Option<i32>, stderr: String },
15 Io(std::io::Error),
17}
18
19impl std::fmt::Display for ClaudeCliError {
20 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21 match self {
22 ClaudeCliError::NotInstalled => write!(f, "`claude` CLI not found on PATH"),
23 ClaudeCliError::NonZeroExit { code, stderr } => write!(
24 f,
25 "`claude` exited with status {:?}: {}",
26 code,
27 stderr.trim()
28 ),
29 ClaudeCliError::Io(e) => write!(f, "failed to invoke `claude`: {}", e),
30 }
31 }
32}
33
34impl std::error::Error for ClaudeCliError {}
35
36pub trait ClaudeCli {
37 fn is_available(&self) -> bool;
38 fn marketplace_add(&self, path: &Path) -> Result<(), ClaudeCliError>;
39 fn marketplace_update(&self, name: &str) -> Result<(), ClaudeCliError>;
40 fn plugin_install(&self, slug: &str) -> Result<(), ClaudeCliError>;
41 fn plugin_update(&self, slug: &str) -> Result<(), ClaudeCliError>;
42}
43
44pub struct RealClaudeCli;
45
46impl RealClaudeCli {
47 fn run(&self, args: &[&str]) -> Result<(), ClaudeCliError> {
48 let out = Command::new("claude").args(args).output().map_err(|e| {
49 if e.kind() == std::io::ErrorKind::NotFound {
50 ClaudeCliError::NotInstalled
51 } else {
52 ClaudeCliError::Io(e)
53 }
54 })?;
55 if !out.status.success() {
56 return Err(ClaudeCliError::NonZeroExit {
57 code: out.status.code(),
58 stderr: String::from_utf8_lossy(&out.stderr).to_string(),
59 });
60 }
61 Ok(())
62 }
63}
64
65impl ClaudeCli for RealClaudeCli {
66 fn is_available(&self) -> bool {
67 Command::new("claude")
68 .arg("--version")
69 .output()
70 .map(|o| o.status.success())
71 .unwrap_or(false)
72 }
73 fn marketplace_add(&self, path: &Path) -> Result<(), ClaudeCliError> {
74 let path_str = path.display().to_string();
76 self.run(&["plugin", "marketplace", "add", &path_str, "--scope", "user"])
77 }
78 fn marketplace_update(&self, name: &str) -> Result<(), ClaudeCliError> {
81 self.run(&["plugin", "marketplace", "update", name])
82 }
83 fn plugin_install(&self, slug: &str) -> Result<(), ClaudeCliError> {
84 self.run(&["plugin", "install", slug, "--scope", "user"])
85 }
86 fn plugin_update(&self, slug: &str) -> Result<(), ClaudeCliError> {
89 self.run(&["plugin", "update", slug])
90 }
91}