From 2cdc3d0403dc5d239303a0ddd8b573b7f9504664 Mon Sep 17 00:00:00 2001 From: Jamie Pine Date: Sat, 21 Jun 2025 17:01:55 -0700 Subject: [PATCH] Enhance daemon functionality with instance support and CLI improvements This commit introduces support for multiple daemon instances, allowing users to specify instance names for better management. The `DaemonConfig` struct is updated to include an optional `instance_name`, and new methods are added for creating and managing instances. The CLI is enhanced with commands to list, stop, and check the status of specific daemon instances. Additionally, the command handling logic is updated to accommodate instance-specific operations, improving user experience and flexibility in managing daemon processes. Documentation is updated to reflect these changes. --- core-new/docs/cli-multi-instance.md | 162 +++++++++++++++++ core-new/src/infrastructure/cli/daemon.rs | 147 ++++++++++++++-- core-new/src/infrastructure/cli/mod.rs | 201 +++++++++++++++++----- 3 files changed, 459 insertions(+), 51 deletions(-) create mode 100644 core-new/docs/cli-multi-instance.md diff --git a/core-new/docs/cli-multi-instance.md b/core-new/docs/cli-multi-instance.md new file mode 100644 index 000000000..3ff4a1ea3 --- /dev/null +++ b/core-new/docs/cli-multi-instance.md @@ -0,0 +1,162 @@ +# Multi-Instance Daemon Support + +Spacedrive CLI now supports running multiple daemon instances simultaneously, enabling local testing of device pairing and other multi-device features. + +## Overview + +Multiple daemon instances allow you to: +- Test device pairing locally by running two instances +- Simulate multi-device scenarios on a single machine +- Isolate different development/testing environments +- Run production and development daemons side-by-side + +## Usage + +### Starting Multiple Instances + +```bash +# Start default instance +spacedrive start + +# Start named instances +spacedrive start --instance alice +spacedrive start --instance bob + +# Start with networking enabled +spacedrive start --instance alice --enable-networking +spacedrive start --instance bob --enable-networking +``` + +### Targeting Specific Instances + +Use the `--instance` flag to target commands to specific daemon instances: + +```bash +# Default instance +spacedrive library list + +# Named instances +spacedrive --instance alice library list +spacedrive --instance bob library create "Bob's Library" +``` + +### Instance Management + +```bash +# List all daemon instances +spacedrive instance list + +# Stop specific instance +spacedrive instance stop alice +spacedrive --instance bob stop # Alternative syntax + +# Check status of specific instance +spacedrive --instance alice daemon +``` + +### Device Pairing Example + +Test device pairing locally using two instances: + +```bash +# Terminal 1: Start Alice's daemon +spacedrive start --instance alice --enable-networking --foreground + +# Terminal 2: Start Bob's daemon +spacedrive start --instance bob --enable-networking --foreground + +# Terminal 3: Alice generates pairing code +spacedrive --instance alice network init --password "test123" +spacedrive --instance alice network pair generate --auto-accept + +# Terminal 4: Bob joins using Alice's code +spacedrive --instance bob network init --password "test123" +spacedrive --instance bob network pair join "word1 word2 word3 ... word12" +``` + +## Architecture + +### Instance Isolation + +Each instance has completely isolated: + +- **Socket paths**: `spacedrive.sock`, `spacedrive-alice.sock`, `spacedrive-bob.sock` +- **PID files**: `spacedrive.pid`, `spacedrive-alice.pid`, `spacedrive-bob.pid` +- **Data directories**: `data/spacedrive-cli-data/`, `data/spacedrive-cli-data/instance-alice/` +- **CLI state**: Separate `cli_state.json` per instance + +### File Structure + +``` +$runtime_dir/ # /tmp or $XDG_RUNTIME_DIR +├── spacedrive.sock # Default instance socket +├── spacedrive.pid # Default instance PID +├── spacedrive-alice.sock # Alice instance socket +├── spacedrive-alice.pid # Alice instance PID +├── spacedrive-bob.sock # Bob instance socket +└── spacedrive-bob.pid # Bob instance PID + +data/spacedrive-cli-data/ # Default instance data +├── spacedrive.json +├── libraries/ +└── cli_state.json + +data/spacedrive-cli-data/instance-alice/ # Alice instance data +├── spacedrive.json +├── libraries/ +└── cli_state.json + +data/spacedrive-cli-data/instance-bob/ # Bob instance data +├── spacedrive.json +├── libraries/ +└── cli_state.json +``` + +## Development Workflow + +### Testing Pairing Protocol + +```bash +# Start two instances for pairing test +spacedrive start --instance initiator --enable-networking --foreground & +spacedrive start --instance joiner --enable-networking --foreground & + +# Initialize networking +spacedrive --instance initiator network init --password "dev123" +spacedrive --instance joiner network init --password "dev123" + +# Test pairing +CODE=$(spacedrive --instance initiator network pair generate --auto-accept | grep "Pairing code:" | cut -d' ' -f3-) +spacedrive --instance joiner network pair join "$CODE" + +# Verify connection +spacedrive --instance initiator network devices +spacedrive --instance joiner network devices +``` + +### Instance Cleanup + +```bash +# Stop all instances +spacedrive instance list +spacedrive instance stop alice +spacedrive instance stop bob +spacedrive stop # Default instance + +# Clean up sockets (if needed) +rm /tmp/spacedrive*.sock /tmp/spacedrive*.pid +``` + +## Backwards Compatibility + +The implementation maintains full backwards compatibility: +- All existing commands work unchanged with the default instance +- No breaking changes to CLI interface +- Default instance behavior is identical to single-instance mode + +## Implementation Notes + +- Instance names must be valid filenames (no special characters) +- Socket discovery happens automatically via filesystem scanning +- Daemon startup checks for instance conflicts +- Each instance runs independently with separate process trees \ No newline at end of file diff --git a/core-new/src/infrastructure/cli/daemon.rs b/core-new/src/infrastructure/cli/daemon.rs index 74a008bd8..ac5dea46c 100644 --- a/core-new/src/infrastructure/cli/daemon.rs +++ b/core-new/src/infrastructure/cli/daemon.rs @@ -18,20 +18,48 @@ pub struct DaemonConfig { pub socket_path: PathBuf, pub pid_file: PathBuf, pub log_file: Option, + pub instance_name: Option, } impl Default for DaemonConfig { fn default() -> Self { + Self::new(None) + } +} + +impl DaemonConfig { + /// Create a new daemon config with optional instance name + pub fn new(instance_name: Option) -> Self { let runtime_dir = dirs::runtime_dir() .or_else(|| dirs::cache_dir()) .unwrap_or_else(|| PathBuf::from("/tmp")); + let (socket_name, pid_name, log_name) = if let Some(ref name) = instance_name { + ( + format!("spacedrive-{}.sock", name), + format!("spacedrive-{}.pid", name), + format!("spacedrive-{}.log", name) + ) + } else { + ( + "spacedrive.sock".to_string(), + "spacedrive.pid".to_string(), + "spacedrive.log".to_string() + ) + }; + Self { - socket_path: runtime_dir.join("spacedrive.sock"), - pid_file: runtime_dir.join("spacedrive.pid"), - log_file: Some(runtime_dir.join("spacedrive.log")), + socket_path: runtime_dir.join(socket_name), + pid_file: runtime_dir.join(pid_name), + log_file: Some(runtime_dir.join(log_name)), + instance_name, } } + + /// Get instance display name ("default" for None, or the actual name) + pub fn instance_display_name(&self) -> &str { + self.instance_name.as_deref().unwrap_or("default") + } } /// Commands that can be sent to the daemon @@ -179,6 +207,14 @@ pub struct Daemon { impl Daemon { /// Create a new daemon instance pub async fn new(data_dir: PathBuf) -> Result> { + Self::new_with_instance(data_dir, None).await + } + + /// Create a new daemon instance with optional instance name + pub async fn new_with_instance( + data_dir: PathBuf, + instance_name: Option, + ) -> Result> { let core = Arc::new(Core::new_with_config(data_dir).await?); // Ensure device is registered for all libraries @@ -231,7 +267,7 @@ impl Daemon { Ok(Self { core, - config: DaemonConfig::default(), + config: DaemonConfig::new(instance_name.clone()), start_time: std::time::Instant::now(), shutdown_tx: Arc::new(tokio::sync::Mutex::new(None)), }) @@ -241,6 +277,15 @@ impl Daemon { pub async fn new_with_networking( data_dir: PathBuf, networking_password: &str + ) -> Result> { + Self::new_with_networking_and_instance(data_dir, networking_password, None).await + } + + /// Create a new daemon instance with networking enabled and optional instance name + pub async fn new_with_networking_and_instance( + data_dir: PathBuf, + networking_password: &str, + instance_name: Option, ) -> Result> { let mut core = Core::new_with_config(data_dir).await?; @@ -300,7 +345,7 @@ impl Daemon { Ok(Self { core, - config: DaemonConfig::default(), + config: DaemonConfig::new(instance_name.clone()), start_time: std::time::Instant::now(), shutdown_tx: Arc::new(tokio::sync::Mutex::new(None)), }) @@ -353,7 +398,12 @@ impl Daemon { /// Check if daemon is running pub fn is_running() -> bool { - let config = DaemonConfig::default(); + Self::is_running_instance(None) + } + + /// Check if daemon instance is running + pub fn is_running_instance(instance_name: Option) -> bool { + let config = DaemonConfig::new(instance_name); if let Ok(pid_str) = std::fs::read_to_string(&config.pid_file) { if let Ok(pid) = pid_str.trim().parse::() { @@ -377,11 +427,16 @@ impl Daemon { /// Stop a running daemon pub async fn stop() -> Result<(), Box> { - let config = DaemonConfig::default(); + Self::stop_instance(None).await + } + + /// Stop a running daemon instance + pub async fn stop_instance(instance_name: Option) -> Result<(), Box> { + let config = DaemonConfig::new(instance_name.clone()); // First check if daemon is actually running - if !Self::is_running() { - return Err("Daemon is not running".into()); + if !Self::is_running_instance(instance_name) { + return Err(format!("Daemon instance '{}' is not running", config.instance_display_name()).into()); } // Try to connect and send shutdown command @@ -417,6 +472,69 @@ impl Daemon { Ok(()) } + + /// List all daemon instances + pub fn list_instances() -> Result, Box> { + let runtime_dir = dirs::runtime_dir() + .or_else(|| dirs::cache_dir()) + .unwrap_or_else(|| PathBuf::from("/tmp")); + + let mut instances = Vec::new(); + + // Find all spacedrive-*.sock files + if let Ok(entries) = std::fs::read_dir(&runtime_dir) { + for entry in entries.flatten() { + let file_name = entry.file_name(); + let file_str = file_name.to_string_lossy(); + + if file_str.starts_with("spacedrive") && file_str.ends_with(".sock") { + let instance_name = if file_str == "spacedrive.sock" { + None // Default instance + } else { + // Extract instance name from spacedrive-{name}.sock + Some(file_str.strip_prefix("spacedrive-") + .and_then(|s| s.strip_suffix(".sock")) + .unwrap_or("unknown") + .to_string()) + }; + + let is_running = Self::is_running_instance(instance_name.clone()); + instances.push(DaemonInstance { + name: instance_name, + socket_path: entry.path(), + is_running, + }); + } + } + } + + // Sort by name for consistent output + instances.sort_by(|a, b| { + match (&a.name, &b.name) { + (None, None) => std::cmp::Ordering::Equal, + (None, Some(_)) => std::cmp::Ordering::Less, // Default first + (Some(_), None) => std::cmp::Ordering::Greater, + (Some(a), Some(b)) => a.cmp(b), + } + }); + + Ok(instances) + } +} + +/// Daemon instance information +#[derive(Debug)] +pub struct DaemonInstance { + pub name: Option, // None for default instance + pub socket_path: PathBuf, + pub is_running: bool, +} + +impl DaemonInstance { + /// Get instance display name (\"default\" for None, or the actual name) + pub fn display_name(&self) -> &str { + self.name.as_deref().unwrap_or("default") + } } /// Handle a client connection @@ -938,12 +1056,19 @@ async fn handle_command( /// Client for communicating with the daemon pub struct DaemonClient { socket_path: PathBuf, + instance_name: Option, } impl DaemonClient { pub fn new() -> Self { + Self::new_with_instance(None) + } + + pub fn new_with_instance(instance_name: Option) -> Self { + let config = DaemonConfig::new(instance_name.clone()); Self { - socket_path: DaemonConfig::default().socket_path, + socket_path: config.socket_path, + instance_name, } } @@ -969,6 +1094,6 @@ impl DaemonClient { /// Check if daemon is running pub fn is_running(&self) -> bool { - Daemon::is_running() + Daemon::is_running_instance(self.instance_name.clone()) } } diff --git a/core-new/src/infrastructure/cli/mod.rs b/core-new/src/infrastructure/cli/mod.rs index 72e06a240..1d8fb9990 100644 --- a/core-new/src/infrastructure/cli/mod.rs +++ b/core-new/src/infrastructure/cli/mod.rs @@ -21,6 +21,10 @@ pub struct Cli { #[arg(short = 'v', long, global = true)] pub verbose: bool, + /// Daemon instance name (for multiple daemon support) + #[arg(long, global = true)] + pub instance: Option, + #[command(subcommand)] pub command: Commands, } @@ -79,11 +83,28 @@ pub enum Commands { /// Check if the daemon is running Daemon, + /// Manage daemon instances + #[command(subcommand)] + Instance(InstanceCommands), + /// Manage device networking and connections #[command(subcommand)] Network(commands::NetworkCommands), } +#[derive(Subcommand, Clone)] +pub enum InstanceCommands { + /// List all daemon instances + List, + /// Stop a specific daemon instance + Stop { + /// Instance name to stop + name: String + }, + /// Show currently targeted instance + Current, +} + pub async fn run() -> Result<(), Box> { let cli = Cli::parse(); @@ -96,10 +117,16 @@ pub async fn run() -> Result<(), Box> { )) .init(); - // Determine data directory - let data_dir = cli + // Determine data directory with instance isolation + let base_data_dir = cli .data_dir .unwrap_or_else(|| PathBuf::from("./data/spacedrive-cli-data")); + + let data_dir = if let Some(ref instance) = cli.instance { + base_data_dir.join(format!("instance-{}", instance)) + } else { + base_data_dir + }; // Handle daemon commands first (they don't need Core) match &cli.command { @@ -107,19 +134,27 @@ pub async fn run() -> Result<(), Box> { foreground, enable_networking, } => { - return handle_start_daemon(data_dir, *foreground, *enable_networking).await; + return handle_start_daemon(data_dir, *foreground, *enable_networking, cli.instance.clone()).await; } Commands::Stop => { - return handle_stop_daemon().await; + return handle_stop_daemon(cli.instance.clone()).await; } Commands::Daemon => { - return handle_daemon_status().await; + return handle_daemon_status(cli.instance.clone()).await; + } + Commands::Instance(instance_cmd) => { + return handle_instance_command(instance_cmd.clone()).await; } _ => { // For all other commands, check if daemon is running - if !daemon::Daemon::is_running() { - println!("❌ Spacedrive daemon is not running"); - println!(" Start it with: spacedrive start"); + if !daemon::Daemon::is_running_instance(cli.instance.clone()) { + let instance_display = cli.instance.as_deref().unwrap_or("default"); + println!("❌ Spacedrive daemon instance '{}' is not running", instance_display); + if cli.instance.is_some() { + println!(" Start it with: spacedrive --instance {} start", instance_display); + } else { + println!(" Start it with: spacedrive start"); + } return Ok(()); } } @@ -128,16 +163,16 @@ pub async fn run() -> Result<(), Box> { // For library, location, and job commands, use the daemon match &cli.command { Commands::Library(library_cmd) => { - return handle_library_daemon_command(library_cmd.clone()).await; + return handle_library_daemon_command(library_cmd.clone(), cli.instance.clone()).await; } Commands::Location(location_cmd) => { - return handle_location_daemon_command(location_cmd.clone()).await; + return handle_location_daemon_command(location_cmd.clone(), cli.instance.clone()).await; } Commands::Job(job_cmd) => { - return handle_job_daemon_command(job_cmd.clone()).await; + return handle_job_daemon_command(job_cmd.clone(), cli.instance.clone()).await; } Commands::Network(network_cmd) => { - return handle_network_daemon_command(network_cmd.clone()).await; + return handle_network_daemon_command(network_cmd.clone(), cli.instance.clone()).await; } Commands::Monitor => { // Special case - monitor needs event streaming @@ -151,7 +186,12 @@ pub async fn run() -> Result<(), Box> { // Initialize core (temporary - for commands not yet converted to daemon) let core = Core::new_with_config(data_dir.clone()).await?; - // Load CLI state + // Load CLI state (instance-specific) + let state_path = if cli.instance.is_some() { + data_dir.join("cli_state.json") + } else { + data_dir.join("cli_state.json") + }; let mut state = state::CliState::load(&data_dir)?; // Execute command @@ -168,7 +208,7 @@ pub async fn run() -> Result<(), Box> { } Commands::Monitor => monitor::run_monitor(&core).await?, Commands::Status => commands::handle_status_command(&core, &state).await?, - Commands::Start { .. } | Commands::Stop | Commands::Daemon | Commands::Network(_) => { + Commands::Start { .. } | Commands::Stop | Commands::Daemon | Commands::Instance(_) | Commands::Network(_) => { // These are handled above, should never reach here unreachable!() } @@ -187,9 +227,11 @@ async fn handle_start_daemon( data_dir: PathBuf, foreground: bool, enable_networking: bool, + instance_name: Option, ) -> Result<(), Box> { - if daemon::Daemon::is_running() { - println!("⚠️ Spacedrive daemon is already running"); + if daemon::Daemon::is_running_instance(instance_name.clone()) { + let instance_display = instance_name.as_deref().unwrap_or("default"); + println!("⚠️ Spacedrive daemon instance '{}' is already running", instance_display); return Ok(()); } @@ -205,17 +247,17 @@ async fn handle_start_daemon( println!(" Using default networking configuration."); println!(" Use 'spacedrive network init --password ' to set a custom password."); - match daemon::Daemon::new_with_networking(data_dir.clone(), default_password).await { + match daemon::Daemon::new_with_networking_and_instance(data_dir.clone(), default_password, instance_name.clone()).await { Ok(daemon) => daemon.start().await?, Err(e) => { println!("❌ Failed to start daemon with networking: {}", e); println!(" Falling back to daemon without networking..."); - let daemon = daemon::Daemon::new(data_dir).await?; + let daemon = daemon::Daemon::new_with_instance(data_dir, instance_name.clone()).await?; daemon.start().await?; } } } else { - let daemon = daemon::Daemon::new(data_dir).await?; + let daemon = daemon::Daemon::new_with_instance(data_dir, instance_name.clone()).await?; daemon.start().await?; } } else { @@ -229,6 +271,10 @@ async fn handle_start_daemon( .arg("--data-dir") .arg(data_dir); + if let Some(ref instance) = instance_name { + cmd.arg("--instance").arg(instance); + } + if enable_networking { cmd.arg("--enable-networking"); } @@ -255,45 +301,51 @@ async fn handle_start_daemon( // Wait a bit to see if it started tokio::time::sleep(std::time::Duration::from_secs(2)).await; - if daemon::Daemon::is_running() { - println!("✅ Spacedrive daemon started successfully"); + if daemon::Daemon::is_running_instance(instance_name.clone()) { + let instance_display = instance_name.as_deref().unwrap_or("default"); + println!("✅ Spacedrive daemon instance '{}' started successfully", instance_display); } else { - println!("❌ Failed to start Spacedrive daemon"); + let instance_display = instance_name.as_deref().unwrap_or("default"); + println!("❌ Failed to start Spacedrive daemon instance '{}'", instance_display); } } Ok(()) } -async fn handle_stop_daemon() -> Result<(), Box> { - if !daemon::Daemon::is_running() { - println!("⚠️ Spacedrive daemon is not running"); +async fn handle_stop_daemon(instance_name: Option) -> Result<(), Box> { + if !daemon::Daemon::is_running_instance(instance_name.clone()) { + let instance_display = instance_name.as_deref().unwrap_or("default"); + println!("⚠️ Spacedrive daemon instance '{}' is not running", instance_display); return Ok(()); } - println!("🛑 Stopping Spacedrive daemon..."); - daemon::Daemon::stop().await?; + let instance_display = instance_name.as_deref().unwrap_or("default"); + println!("🛑 Stopping Spacedrive daemon instance '{}'...", instance_display); + daemon::Daemon::stop_instance(instance_name.clone()).await?; // Wait a bit to ensure it's stopped tokio::time::sleep(std::time::Duration::from_secs(1)).await; - if !daemon::Daemon::is_running() { - println!("✅ Spacedrive daemon stopped"); + if !daemon::Daemon::is_running_instance(instance_name.clone()) { + println!("✅ Spacedrive daemon instance '{}' stopped", instance_display); } else { - println!("❌ Failed to stop Spacedrive daemon"); + println!("❌ Failed to stop Spacedrive daemon instance '{}'", instance_display); } Ok(()) } -async fn handle_daemon_status() -> Result<(), Box> { +async fn handle_daemon_status(instance_name: Option) -> Result<(), Box> { use colored::Colorize; - if daemon::Daemon::is_running() { - println!("✅ Spacedrive daemon is running"); + let instance_display = instance_name.as_deref().unwrap_or("default"); + + if daemon::Daemon::is_running_instance(instance_name.clone()) { + println!("✅ Spacedrive daemon instance '{}' is running", instance_display); // Try to get more info from daemon - let client = daemon::DaemonClient::new(); + let client = daemon::DaemonClient::new_with_instance(instance_name); // Get status match client.send_command(daemon::DaemonCommand::GetStatus).await { @@ -364,8 +416,73 @@ async fn handle_daemon_status() -> Result<(), Box> { _ => {} } } else { - println!("❌ Spacedrive daemon is not running"); - println!(" Start it with: spacedrive start"); + println!("❌ Spacedrive daemon instance '{}' is not running", instance_display); + if instance_name.is_some() { + println!(" Start it with: spacedrive --instance {} start", instance_display); + } else { + println!(" Start it with: spacedrive start"); + } + } + + Ok(()) +} + +async fn handle_instance_command( + cmd: InstanceCommands, +) -> Result<(), Box> { + use colored::Colorize; + + match cmd { + InstanceCommands::List => { + match daemon::Daemon::list_instances() { + Ok(instances) => { + if instances.is_empty() { + println!("📭 No daemon instances found"); + } else { + use comfy_table::Table; + let mut table = Table::new(); + table.set_header(vec!["Instance", "Status", "Socket Path"]); + + for instance in instances { + let status = if instance.is_running { + "Running".green() + } else { + "Stopped".red() + }; + + table.add_row(vec![ + instance.display_name().to_string(), + status.to_string(), + instance.socket_path.display().to_string(), + ]); + } + + println!("{}", table); + } + } + Err(e) => { + println!("❌ Failed to list instances: {}", e); + } + } + } + + InstanceCommands::Stop { name } => { + let instance_name = if name == "default" { None } else { Some(name.clone()) }; + match daemon::Daemon::stop_instance(instance_name).await { + Ok(_) => { + println!("✅ Daemon instance '{}' stopped", name); + } + Err(e) => { + println!("❌ Failed to stop instance '{}': {}", name, e); + } + } + } + + InstanceCommands::Current => { + // This would show the current instance based on CLI args or context + println!("Current instance functionality not yet implemented"); + println!("Use --instance flag to target specific instances"); + } } Ok(()) @@ -373,10 +490,11 @@ async fn handle_daemon_status() -> Result<(), Box> { async fn handle_library_daemon_command( cmd: commands::LibraryCommands, + instance_name: Option, ) -> Result<(), Box> { use colored::Colorize; - let client = daemon::DaemonClient::new(); + let client = daemon::DaemonClient::new_with_instance(instance_name.clone()); match cmd { commands::LibraryCommands::Create { name, path } => { @@ -500,10 +618,11 @@ async fn handle_library_daemon_command( async fn handle_location_daemon_command( cmd: commands::LocationCommands, + instance_name: Option, ) -> Result<(), Box> { use colored::Colorize; - let client = daemon::DaemonClient::new(); + let client = daemon::DaemonClient::new_with_instance(instance_name.clone()); match cmd { commands::LocationCommands::Add { path, name, mode } => { @@ -700,10 +819,11 @@ async fn handle_location_daemon_command( async fn handle_job_daemon_command( cmd: commands::JobCommands, + instance_name: Option, ) -> Result<(), Box> { use colored::Colorize; - let client = daemon::DaemonClient::new(); + let client = daemon::DaemonClient::new_with_instance(instance_name.clone()); match cmd { commands::JobCommands::List { status, recent: _ } => { @@ -898,10 +1018,11 @@ async fn handle_job_daemon_command( async fn handle_network_daemon_command( cmd: commands::NetworkCommands, + instance_name: Option, ) -> Result<(), Box> { use colored::Colorize; - let client = daemon::DaemonClient::new(); + let client = daemon::DaemonClient::new_with_instance(instance_name.clone()); // Check if daemon is running for most commands match &cmd {