pub mod command;
pub mod error;
pub mod exec;
pub mod mcp_config;
pub mod retry;
#[cfg(feature = "json")]
pub mod session;
pub mod streaming;
pub mod types;
pub mod version;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::time::Duration;
pub use command::ClaudeCommand;
pub use command::agents::AgentsCommand;
pub use command::auth::{
AuthLoginCommand, AuthLogoutCommand, AuthStatusCommand, SetupTokenCommand,
};
pub use command::doctor::DoctorCommand;
pub use command::marketplace::{
MarketplaceAddCommand, MarketplaceListCommand, MarketplaceRemoveCommand,
MarketplaceUpdateCommand,
};
pub use command::mcp::{
McpAddCommand, McpAddFromDesktopCommand, McpAddJsonCommand, McpGetCommand, McpListCommand,
McpRemoveCommand, McpResetProjectChoicesCommand, McpServeCommand,
};
pub use command::plugin::{
PluginDisableCommand, PluginEnableCommand, PluginInstallCommand, PluginListCommand,
PluginUninstallCommand, PluginUpdateCommand, PluginValidateCommand,
};
pub use command::query::QueryCommand;
pub use command::raw::RawCommand;
pub use command::version::VersionCommand;
pub use error::{Error, Result};
pub use exec::CommandOutput;
#[cfg(feature = "tempfile")]
pub use mcp_config::TempMcpConfig;
pub use mcp_config::{McpConfigBuilder, McpServerConfig};
pub use retry::{BackoffStrategy, RetryPolicy};
#[cfg(feature = "json")]
pub use session::Session;
pub use types::*;
pub use version::{CliVersion, VersionParseError};
#[derive(Debug, Clone)]
pub struct Claude {
pub(crate) binary: PathBuf,
pub(crate) working_dir: Option<PathBuf>,
pub(crate) env: HashMap<String, String>,
pub(crate) global_args: Vec<String>,
pub(crate) timeout: Option<Duration>,
pub(crate) retry_policy: Option<RetryPolicy>,
}
impl Claude {
#[must_use]
pub fn builder() -> ClaudeBuilder {
ClaudeBuilder::default()
}
#[must_use]
pub fn binary(&self) -> &Path {
&self.binary
}
#[must_use]
pub fn working_dir(&self) -> Option<&Path> {
self.working_dir.as_deref()
}
#[must_use]
pub fn with_working_dir(&self, dir: impl Into<PathBuf>) -> Self {
let mut clone = self.clone();
clone.working_dir = Some(dir.into());
clone
}
pub async fn cli_version(&self) -> Result<CliVersion> {
let output = VersionCommand::new().execute(self).await?;
CliVersion::parse_version_output(&output.stdout).map_err(|e| Error::Io {
message: format!("failed to parse CLI version: {e}"),
source: std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()),
working_dir: None,
})
}
pub async fn check_version(&self, minimum: &CliVersion) -> Result<CliVersion> {
let version = self.cli_version().await?;
if version.satisfies_minimum(minimum) {
Ok(version)
} else {
Err(Error::VersionMismatch {
found: version,
minimum: *minimum,
})
}
}
}
#[derive(Debug, Default)]
pub struct ClaudeBuilder {
binary: Option<PathBuf>,
working_dir: Option<PathBuf>,
env: HashMap<String, String>,
global_args: Vec<String>,
timeout: Option<Duration>,
retry_policy: Option<RetryPolicy>,
}
impl ClaudeBuilder {
#[must_use]
pub fn binary(mut self, path: impl Into<PathBuf>) -> Self {
self.binary = Some(path.into());
self
}
#[must_use]
pub fn working_dir(mut self, path: impl Into<PathBuf>) -> Self {
self.working_dir = Some(path.into());
self
}
#[must_use]
pub fn env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.env.insert(key.into(), value.into());
self
}
#[must_use]
pub fn envs(
mut self,
vars: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
) -> Self {
for (k, v) in vars {
self.env.insert(k.into(), v.into());
}
self
}
#[must_use]
pub fn timeout_secs(mut self, seconds: u64) -> Self {
self.timeout = Some(Duration::from_secs(seconds));
self
}
#[must_use]
pub fn timeout(mut self, duration: Duration) -> Self {
self.timeout = Some(duration);
self
}
#[must_use]
pub fn arg(mut self, arg: impl Into<String>) -> Self {
self.global_args.push(arg.into());
self
}
#[must_use]
pub fn verbose(mut self) -> Self {
self.global_args.push("--verbose".into());
self
}
#[must_use]
pub fn debug(mut self) -> Self {
self.global_args.push("--debug".into());
self
}
#[must_use]
pub fn retry(mut self, policy: RetryPolicy) -> Self {
self.retry_policy = Some(policy);
self
}
pub fn build(self) -> Result<Claude> {
let binary = match self.binary {
Some(path) => path,
None => which::which("claude").map_err(|_| Error::NotFound)?,
};
Ok(Claude {
binary,
working_dir: self.working_dir,
env: self.env,
global_args: self.global_args,
timeout: self.timeout,
retry_policy: self.retry_policy,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_builder_with_binary() {
let claude = Claude::builder()
.binary("/usr/local/bin/claude")
.env("FOO", "bar")
.timeout_secs(60)
.build()
.unwrap();
assert_eq!(claude.binary, PathBuf::from("/usr/local/bin/claude"));
assert_eq!(claude.env.get("FOO").unwrap(), "bar");
assert_eq!(claude.timeout, Some(Duration::from_secs(60)));
}
#[test]
fn test_builder_global_args() {
let claude = Claude::builder()
.binary("/usr/local/bin/claude")
.arg("--verbose")
.build()
.unwrap();
assert_eq!(claude.global_args, vec!["--verbose"]);
}
#[test]
fn test_builder_verbose() {
let claude = Claude::builder()
.binary("/usr/local/bin/claude")
.verbose()
.build()
.unwrap();
assert!(claude.global_args.contains(&"--verbose".to_string()));
}
#[test]
fn test_builder_debug() {
let claude = Claude::builder()
.binary("/usr/local/bin/claude")
.debug()
.build()
.unwrap();
assert!(claude.global_args.contains(&"--debug".to_string()));
}
}