AirLibrary/CLI/
mod.rs

1//! # CLI - Command Line Interface
2//!
3//! ## Responsibilities
4//!
5//! This module provides the comprehensive command-line interface for the Air
6//! daemon, serving as the primary interface for users and administrators to
7//! interact with a running Air instance. The CLI is responsible for:
8//!
9//! - **Command Parsing and Validation**: Parsing command-line arguments,
10//!   validating inputs, and providing helpful error messages for invalid
11//!   commands or arguments
12//! - **Command Routing**: Routing commands to the appropriate handlers and
13//!   executing them
14//! - **Configuration Management**: Reading, setting, validating, and reloading
15//!   configuration
16//! - **Status and Health Monitoring**: Querying daemon status, service health,
17//!   and metrics
18//! - **Log Management**: Viewing and filtering daemon and service logs
19//! - **Debugging and Diagnostics**: Providing tools for debugging and
20//!   diagnosing issues
21//! - **Output Formatting**: Presenting output in human-readable (table, plain)
22//!   or machine-readable (JSON) formats
23//! - **Daemon Communication**: Establishing and managing connections to the
24//!   running Air daemon
25//! - **Permission Management**: Enforcing security and permission checks for
26//!   sensitive operations
27//!
28//! ## VSCode CLI Patterns
29//!
30//! This implementation draws inspiration from VSCode's CLI architecture:
31//! - Reference: vs/platform/environment/common/environment.ts
32//! - Reference: vs/platform/remote/common/remoteAgentConnection.ts
33//!
34//! Patterns adopted from VSCode CLI:
35//! - Subcommand hierarchy with nested commands and options
36//! - Multiple output formats (JSON, human-readable)
37//! - Comprehensive help system with per-command documentation
38//! - Status and health check capabilities
39//! - Configuration management with validation
40//! - Service-specific operations
41//! - Connection management to running daemon processes
42//! - Extension/plugin compatibility with the daemon
43//!
44//! ## TODO: Future Enhancements
45//!
46//! - **Plugin Marketplace Integration**: Add commands for discovering,
47//!   installing, and managing plugins from a central marketplace (similar to
48//!   `code --install-extension`)
49//! - **Hot Reload Support**: Implement hot reload of configuration and plugins
50//!   without daemon restart
51//! - **Sandboxing Mode**: Add a sandboxed mode for running commands with
52//!   restricted permissions
53//! - **Interactive Shell**: Implement an interactive shell mode for continuous
54//!   daemon interaction
55//! - **Completion Scripts**: Generate shell completion scripts (bash, zsh,
56//!   fish) for better UX
57//! - **Profile Management**: Support multiple configuration profiles for
58//!   different environments
59//! - **Remote Management**: Add support for managing remote Air instances via
60//!   SSH/IPC
61//! - **Audit Logging**: Add comprehensive audit logging for all administrative
62//!   actions
63//!
64//! ## Security Considerations
65//!
66//! - Admin commands (restart, config set) require elevated privileges
67//! - Daemon communication uses secure IPC channels
68//! - Sensitive information is masked in logs and error messages
69//! - Timeouts prevent hanging on unresponsive daemon
70
71use std::{collections::HashMap, time::Duration};
72
73use serde::{Deserialize, Serialize};
74use chrono::{DateTime, Utc};
75
76// =============================================================================
77// Command Types
78// =============================================================================
79
80/// Main CLI command enum
81#[derive(Debug, Clone)]
82pub enum Command {
83	/// Status command - check daemon and service status
84	Status { service:Option<String>, verbose:bool, json:bool },
85	/// Restart command - restart services
86	Restart { service:Option<String>, force:bool },
87	/// Configuration commands
88	Config(ConfigCommand),
89	/// Metrics command - retrieve performance metrics
90	Metrics { json:bool, service:Option<String> },
91	/// Logs command - view daemon logs
92	Logs { service:Option<String>, tail:Option<usize>, filter:Option<String>, follow:bool },
93	/// Debug commands
94	Debug(DebugCommand),
95	/// Help command
96	Help { command:Option<String> },
97	/// Version command
98	Version,
99}
100
101/// Configuration subcommands
102#[derive(Debug, Clone)]
103pub enum ConfigCommand {
104	/// Get configuration value
105	Get { key:String },
106	/// Set configuration value
107	Set { key:String, value:String },
108	/// Reload configuration from file
109	Reload { validate:bool },
110	/// Show current configuration
111	Show { json:bool },
112	/// Validate configuration
113	Validate { path:Option<String> },
114}
115
116/// Debug subcommands
117#[derive(Debug, Clone)]
118pub enum DebugCommand {
119	/// Dump current daemon state
120	DumpState { service:Option<String>, json:bool },
121	/// Dump active connections
122	DumpConnections { format:Option<String> },
123	/// Perform health check
124	HealthCheck { verbose:bool, service:Option<String> },
125	/// Advanced diagnostics
126	Diagnostics { level:DiagnosticLevel },
127}
128
129/// Diagnostic level
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131pub enum DiagnosticLevel {
132	Basic,
133	Extended,
134	Full,
135}
136
137/// Command validation result
138#[derive(Debug, Clone)]
139pub enum ValidationResult {
140	Valid,
141	Invalid(String),
142}
143
144/// Permission level required for a command
145#[derive(Debug, Clone, Copy, PartialEq, Eq)]
146pub enum PermissionLevel {
147	/// No special permission required
148	User,
149	/// Elevated permissions required (e.g., sudo on Unix, Admin on Windows)
150	Admin,
151}
152
153// =============================================================================
154// CLI Arguments Parsing and Validation
155// =============================================================================
156
157/// CLI arguments parser with validation
158#[allow(dead_code)]
159pub struct CliParser {
160	#[allow(dead_code)]
161	TimeoutSecs:u64,
162}
163
164impl CliParser {
165	/// Create a new CLI parser with default timeout
166	pub fn new() -> Self { Self { TimeoutSecs:30 } }
167
168	/// Create a new CLI parser with custom timeout
169	pub fn with_timeout(TimeoutSecs:u64) -> Self { Self { TimeoutSecs } }
170
171	/// Parse command line arguments into Command
172	pub fn parse(args:Vec<String>) -> Result<Command, String> { Self::new().parse_args(args) }
173
174	/// Parse command line arguments into Command with timeout setting
175	pub fn parse_args(&self, args:Vec<String>) -> Result<Command, String> {
176		// Remove program name
177		let args = if args.is_empty() { vec![] } else { args[1..].to_vec() };
178
179		if args.is_empty() {
180			return Ok(Command::Help { command:None });
181		}
182
183		let command = &args[0];
184
185		match command.as_str() {
186			"status" => self.parse_status(&args[1..]),
187			"restart" => self.parse_restart(&args[1..]),
188			"config" => self.parse_config(&args[1..]),
189			"metrics" => self.parse_metrics(&args[1..]),
190			"logs" => self.parse_logs(&args[1..]),
191			"debug" => self.parse_debug(&args[1..]),
192			"help" | "-h" | "--help" => self.parse_help(&args[1..]),
193			"version" | "-v" | "--version" => Ok(Command::Version),
194			_ => {
195				Err(format!(
196					"Unknown command: {}\n\nUse 'Air help' for available commands.",
197					command
198				))
199			},
200		}
201	}
202
203	/// Parse status command with validation
204	fn parse_status(&self, args:&[String]) -> Result<Command, String> {
205		let mut service = None;
206		let mut verbose = false;
207		let mut json = false;
208
209		let mut i = 0;
210		while i < args.len() {
211			match args[i].as_str() {
212				"--service" => {
213					if i + 1 < args.len() {
214						service = Some(args[i + 1].clone());
215						Self::validate_service_name(&service)?;
216						i += 2;
217					} else {
218						return Err("--service requires a value".to_string());
219					}
220				},
221				"-s" => {
222					if i + 1 < args.len() {
223						service = Some(args[i + 1].clone());
224						Self::validate_service_name(&service)?;
225						i += 2;
226					} else {
227						return Err("-s requires a value".to_string());
228					}
229				},
230				"--verbose" | "-v" => {
231					verbose = true;
232					i += 1;
233				},
234				"--json" => {
235					json = true;
236					i += 1;
237				},
238				_ => {
239					return Err(format!(
240						"Unknown flag for 'status' command: {}\n\nValid flags are: --service, --verbose, --json",
241						args[i]
242					));
243				},
244			}
245		}
246
247		Ok(Command::Status { service, verbose, json })
248	}
249
250	/// Parse restart command with validation
251	fn parse_restart(&self, args:&[String]) -> Result<Command, String> {
252		let mut service = None;
253		let mut force = false;
254
255		let mut i = 0;
256		while i < args.len() {
257			match args[i].as_str() {
258				"--service" | "-s" => {
259					if i + 1 < args.len() {
260						service = Some(args[i + 1].clone());
261						Self::validate_service_name(&service)?;
262						i += 2;
263					} else {
264						return Err("--service requires a value".to_string());
265					}
266				},
267				"--force" | "-f" => {
268					force = true;
269					i += 1;
270				},
271				_ => {
272					return Err(format!(
273						"Unknown flag for 'restart' command: {}\n\nValid flags are: --service, --force",
274						args[i]
275					));
276				},
277			}
278		}
279
280		Ok(Command::Restart { service, force })
281	}
282
283	/// Parse config subcommand with validation
284	fn parse_config(&self, args:&[String]) -> Result<Command, String> {
285		if args.is_empty() {
286			return Err(
287				"config requires a subcommand: get, set, reload, show, validate\n\nUse 'Air help config' for more \
288				 information."
289					.to_string(),
290			);
291		}
292
293		let subcommand = &args[0];
294
295		match subcommand.as_str() {
296			"get" => {
297				if args.len() < 2 {
298					return Err("config get requires a key\n\nExample: Air config get grpc.BindAddress".to_string());
299				}
300				let key = args[1].clone();
301				Self::validate_config_key(&key)?;
302				Ok(Command::Config(ConfigCommand::Get { key }))
303			},
304			"set" => {
305				if args.len() < 3 {
306					return Err("config set requires key and value\n\nExample: Air config set grpc.BindAddress \
307					            \"[::1]:50053\""
308						.to_string());
309				}
310				let key = args[1].clone();
311				let value = args[2].clone();
312				Self::validate_config_key(&key)?;
313				Self::validate_config_value(&key, &value)?;
314				Ok(Command::Config(ConfigCommand::Set { key, value }))
315			},
316			"reload" => {
317				let validate = args.contains(&"--validate".to_string());
318				Ok(Command::Config(ConfigCommand::Reload { validate }))
319			},
320			"show" => {
321				let json = args.contains(&"--json".to_string());
322				Ok(Command::Config(ConfigCommand::Show { json }))
323			},
324			"validate" => {
325				let path = args.get(1).cloned();
326				if let Some(p) = &path {
327					Self::validate_config_path(p)?;
328				}
329				Ok(Command::Config(ConfigCommand::Validate { path }))
330			},
331			_ => {
332				Err(format!(
333					"Unknown config subcommand: {}\n\nValid subcommands are: get, set, reload, show, validate",
334					subcommand
335				))
336			},
337		}
338	}
339
340	/// Parse metrics command with validation
341	fn parse_metrics(&self, args:&[String]) -> Result<Command, String> {
342		let mut json = false;
343		let mut service = None;
344
345		let mut i = 0;
346		while i < args.len() {
347			match args[i].as_str() {
348				"--json" => {
349					json = true;
350					i += 1;
351				},
352				"--service" | "-s" => {
353					if i + 1 < args.len() {
354						service = Some(args[i + 1].clone());
355						Self::validate_service_name(&service)?;
356						i += 2;
357					} else {
358						return Err("--service requires a value".to_string());
359					}
360				},
361				_ => {
362					return Err(format!(
363						"Unknown flag for 'metrics' command: {}\n\nValid flags are: --service, --json",
364						args[i]
365					));
366				},
367			}
368		}
369
370		Ok(Command::Metrics { json, service })
371	}
372
373	/// Parse logs command with validation
374	fn parse_logs(&self, args:&[String]) -> Result<Command, String> {
375		let mut service = None;
376		let mut tail = None;
377		let mut filter = None;
378		let mut follow = false;
379
380		let mut i = 0;
381		while i < args.len() {
382			match args[i].as_str() {
383				"--service" | "-s" => {
384					if i + 1 < args.len() {
385						service = Some(args[i + 1].clone());
386						Self::validate_service_name(&service)?;
387						i += 2;
388					} else {
389						return Err("--service requires a value".to_string());
390					}
391				},
392				"--tail" | "-n" => {
393					if i + 1 < args.len() {
394						tail = Some(args[i + 1].parse::<usize>().map_err(|_| {
395							format!("Invalid tail value '{}': must be a positive integer", args[i + 1])
396						})?);
397						if tail.unwrap_or(0) == 0 {
398							return Err("Invalid tail value: must be a positive integer".to_string());
399						}
400						i += 2;
401					} else {
402						return Err("--tail requires a value".to_string());
403					}
404				},
405				"--filter" | "-f" => {
406					if i + 1 < args.len() {
407						filter = Some(args[i + 1].clone());
408						Self::validate_filter_pattern(&filter)?;
409						i += 2;
410					} else {
411						return Err("--filter requires a value".to_string());
412					}
413				},
414				"--follow" => {
415					follow = true;
416					i += 1;
417				},
418				_ => {
419					return Err(format!(
420						"Unknown flag for 'logs' command: {}\n\nValid flags are: --service, --tail, --filter, --follow",
421						args[i]
422					));
423				},
424			}
425		}
426
427		Ok(Command::Logs { service, tail, filter, follow })
428	}
429
430	/// Parse debug subcommand with validation
431	fn parse_debug(&self, args:&[String]) -> Result<Command, String> {
432		if args.is_empty() {
433			return Err(
434				"debug requires a subcommand: dump-state, dump-connections, health-check, diagnostics\n\nUse 'Air \
435				 help debug' for more information."
436					.to_string(),
437			);
438		}
439
440		let subcommand = &args[0];
441
442		match subcommand.as_str() {
443			"dump-state" => {
444				let mut service = None;
445				let mut json = false;
446
447				let mut i = 1;
448				while i < args.len() {
449					match args[i].as_str() {
450						"--service" | "-s" => {
451							if i + 1 < args.len() {
452								service = Some(args[i + 1].clone());
453								Self::validate_service_name(&service)?;
454								i += 2;
455							} else {
456								return Err("--service requires a value".to_string());
457							}
458						},
459						"--json" => {
460							json = true;
461							i += 1;
462						},
463						_ => {
464							return Err(format!(
465								"Unknown flag for 'debug dump-state': {}\n\nValid flags are: --service, --json",
466								args[i]
467							));
468						},
469					}
470				}
471
472				Ok(Command::Debug(DebugCommand::DumpState { service, json }))
473			},
474			"dump-connections" => {
475				let mut format = None;
476				let mut i = 1;
477				while i < args.len() {
478					match args[i].as_str() {
479						"--format" | "-f" => {
480							if i + 1 < args.len() {
481								format = Some(args[i + 1].clone());
482								Self::validate_output_format(&format)?;
483								i += 2;
484							} else {
485								return Err("--format requires a value (json, table, plain)".to_string());
486							}
487						},
488						_ => {
489							return Err(format!(
490								"Unknown flag for 'debug dump-connections': {}\n\nValid flags are: --format",
491								args[i]
492							));
493						},
494					}
495				}
496				Ok(Command::Debug(DebugCommand::DumpConnections { format }))
497			},
498			"health-check" => {
499				let verbose = args.contains(&"--verbose".to_string());
500				let mut service = None;
501
502				let mut i = 1;
503				while i < args.len() {
504					match args[i].as_str() {
505						"--service" | "-s" => {
506							if i + 1 < args.len() {
507								service = Some(args[i + 1].clone());
508								Self::validate_service_name(&service)?;
509								i += 2;
510							} else {
511								return Err("--service requires a value".to_string());
512							}
513						},
514						"--verbose" | "-v" => {
515							i += 1;
516						},
517						_ => {
518							return Err(format!(
519								"Unknown flag for 'debug health-check': {}\n\nValid flags are: --service, --verbose",
520								args[i]
521							));
522						},
523					}
524				}
525
526				Ok(Command::Debug(DebugCommand::HealthCheck { verbose, service }))
527			},
528			"diagnostics" => {
529				let mut level = DiagnosticLevel::Basic;
530
531				let mut i = 1;
532				while i < args.len() {
533					match args[i].as_str() {
534						"--full" => {
535							level = DiagnosticLevel::Full;
536							i += 1;
537						},
538						"--extended" => {
539							level = DiagnosticLevel::Extended;
540							i += 1;
541						},
542						"--basic" => {
543							level = DiagnosticLevel::Basic;
544							i += 1;
545						},
546						_ => {
547							return Err(format!(
548								"Unknown flag for 'debug diagnostics': {}\n\nValid flags are: --basic, --extended, \
549								 --full",
550								args[i]
551							));
552						},
553					}
554				}
555
556				Ok(Command::Debug(DebugCommand::Diagnostics { level }))
557			},
558			_ => {
559				Err(format!(
560					"Unknown debug subcommand: {}\n\nValid subcommands are: dump-state, dump-connections, \
561					 health-check, diagnostics",
562					subcommand
563				))
564			},
565		}
566	}
567
568	/// Parse help command
569	fn parse_help(&self, args:&[String]) -> Result<Command, String> {
570		let command = args.get(0).map(|s| s.clone());
571		Ok(Command::Help { command })
572	}
573
574	// =============================================================================
575	// Validation Methods
576	// =============================================================================
577
578	/// Validate service name format
579	fn validate_service_name(service:&Option<String>) -> Result<(), String> {
580		if let Some(s) = service {
581			if s.is_empty() {
582				return Err("Service name cannot be empty".to_string());
583			}
584			if s.len() > 100 {
585				return Err("Service name too long (max 100 characters)".to_string());
586			}
587			if !s.chars().all(|c| c.is_alphanumeric() || c == '-' || c == '_') {
588				return Err(
589					"Service name can only contain alphanumeric characters, hyphens, and underscores".to_string(),
590				);
591			}
592		}
593		Ok(())
594	}
595
596	/// Validate configuration key format
597	fn validate_config_key(key:&str) -> Result<(), String> {
598		if key.is_empty() {
599			return Err("Configuration key cannot be empty".to_string());
600		}
601		if key.len() > 255 {
602			return Err("Configuration key too long (max 255 characters)".to_string());
603		}
604		if !key.contains('.') {
605			return Err("Configuration key must use dot notation (e.g., 'section.subsection.key')".to_string());
606		}
607		let parts:Vec<&str> = key.split('.').collect();
608		for part in &parts {
609			if part.is_empty() {
610				return Err("Configuration key cannot have empty segments (e.g., 'section..key')".to_string());
611			}
612			if !part.chars().all(|c| c.is_alphanumeric() || c == '_' || c == '-') {
613				return Err(format!("Invalid configuration key segment '{}': must be alphanumeric", part));
614			}
615		}
616		Ok(())
617	}
618
619	/// Validate configuration value
620	fn validate_config_value(key:&str, value:&str) -> Result<(), String> {
621		if value.is_empty() {
622			return Err("Configuration value cannot be empty".to_string());
623		}
624		if value.len() > 10000 {
625			return Err("Configuration value too long (max 10000 characters)".to_string());
626		}
627
628		// Validate specific keys
629		if key.contains("bind_address") || key.contains("listen") {
630			Self::validate_bind_address(value)?;
631		}
632
633		Ok(())
634	}
635
636	/// Validate bind address format
637	fn validate_bind_address(address:&str) -> Result<(), String> {
638		if address.is_empty() {
639			return Err("Bind address cannot be empty".to_string());
640		}
641		if address.starts_with("127.0.0.1") || address.starts_with("[::1]") || address == "0.0.0.0" || address == "::" {
642			return Ok(());
643		}
644		return Err("Invalid bind address format".to_string());
645	}
646
647	/// Validate configuration file path
648	fn validate_config_path(path:&str) -> Result<(), String> {
649		if path.is_empty() {
650			return Err("Configuration path cannot be empty".to_string());
651		}
652		if !path.ends_with(".json") && !path.ends_with(".toml") && !path.ends_with(".yaml") && !path.ends_with(".yml") {
653			return Err("Configuration file must be .json, .toml, .yaml, or .yml".to_string());
654		}
655		Ok(())
656	}
657
658	/// Validate log filter pattern
659	fn validate_filter_pattern(filter:&Option<String>) -> Result<(), String> {
660		if let Some(f) = filter {
661			if f.is_empty() {
662				return Err("Filter pattern cannot be empty".to_string());
663			}
664			if f.len() > 1000 {
665				return Err("Filter pattern too long (max 1000 characters)".to_string());
666			}
667		}
668		Ok(())
669	}
670
671	/// Validate output format
672	fn validate_output_format(format:&Option<String>) -> Result<(), String> {
673		if let Some(f) = format {
674			match f.as_str() {
675				"json" | "table" | "plain" => Ok(()),
676				_ => Err(format!("Invalid output format '{}'. Valid formats: json, table, plain", f)),
677			}
678		} else {
679			Ok(())
680		}
681	}
682}
683
684// =============================================================================
685// Response Structures
686// =============================================================================
687
688/// Status response
689#[derive(Debug, Serialize, Deserialize)]
690pub struct StatusResponse {
691	pub daemon_running:bool,
692	pub uptime_secs:u64,
693	pub version:String,
694	pub services:HashMap<String, ServiceStatus>,
695	pub timestamp:String,
696}
697
698/// Service status entry
699#[derive(Debug, Serialize, Deserialize)]
700pub struct ServiceStatus {
701	pub name:String,
702	pub running:bool,
703	pub health:ServiceHealth,
704	pub uptime_secs:u64,
705	pub error:Option<String>,
706}
707
708/// Service health status
709#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
710#[serde(rename_all = "UPPERCASE")]
711pub enum ServiceHealth {
712	Healthy,
713	Degraded,
714	Unhealthy,
715	Unknown,
716}
717
718/// Metrics response
719#[derive(Debug, Serialize, Deserialize)]
720pub struct MetricsResponse {
721	pub timestamp:String,
722	pub memory_used_mb:f64,
723	pub memory_available_mb:f64,
724	pub cpu_usage_percent:f64,
725	pub disk_used_mb:u64,
726	pub disk_available_mb:u64,
727	pub active_connections:u32,
728	pub processed_requests:u64,
729	pub failed_requests:u64,
730	pub service_metrics:HashMap<String, ServiceMetrics>,
731}
732
733/// Service metrics entry
734#[derive(Debug, Serialize, Deserialize)]
735pub struct ServiceMetrics {
736	pub name:String,
737	pub requests_total:u64,
738	pub requests_success:u64,
739	pub requests_failed:u64,
740	pub average_latency_ms:f64,
741	pub p99_latency_ms:f64,
742}
743
744/// Health check response
745#[derive(Debug, Serialize, Deserialize)]
746pub struct HealthCheckResponse {
747	pub overall_healthy:bool,
748	pub overall_health_percentage:f64,
749	pub services:HashMap<String, ServiceHealthDetail>,
750	pub timestamp:String,
751}
752
753/// Detailed service health
754#[derive(Debug, Serialize, Deserialize)]
755pub struct ServiceHealthDetail {
756	pub name:String,
757	pub healthy:bool,
758	pub response_time_ms:u64,
759	pub last_check:String,
760	pub details:String,
761}
762
763/// Configuration response
764#[derive(Debug, Serialize, Deserialize)]
765pub struct ConfigResponse {
766	pub key:Option<String>,
767	pub value:serde_json::Value,
768	pub path:String,
769	pub modified:String,
770}
771
772/// Log entry
773#[derive(Debug, Serialize, Deserialize)]
774pub struct LogEntry {
775	pub timestamp:DateTime<Utc>,
776	pub level:String,
777	pub service:Option<String>,
778	pub message:String,
779	pub context:Option<serde_json::Value>,
780}
781
782/// Connection info
783#[derive(Debug, Serialize, Deserialize)]
784pub struct ConnectionInfo {
785	pub id:String,
786	pub remote_address:String,
787	pub connected_at:DateTime<Utc>,
788	pub service:Option<String>,
789	pub active:bool,
790}
791
792/// Daemon state dump
793#[derive(Debug, Serialize, Deserialize)]
794pub struct DaemonState {
795	pub timestamp:DateTime<Utc>,
796	pub version:String,
797	pub uptime_secs:u64,
798	pub services:HashMap<String, serde_json::Value>,
799	pub connections:Vec<ConnectionInfo>,
800	pub plugin_state:serde_json::Value,
801}
802
803// =============================================================================
804// Daemon Connection and Client
805// =============================================================================
806
807/// Daemon client for communicating with running Air daemon
808#[allow(dead_code)]
809pub struct DaemonClient {
810	#[allow(dead_code)]
811	address:String,
812	#[allow(dead_code)]
813	timeout:Duration,
814}
815
816impl DaemonClient {
817	/// Create a new daemon client
818	pub fn new(address:String) -> Self { Self { address, timeout:Duration::from_secs(30) } }
819
820	/// Create a new daemon client with custom timeout
821	pub fn with_timeout(address:String, timeout_secs:u64) -> Self {
822		Self { address, timeout:Duration::from_secs(timeout_secs) }
823	}
824
825	/// Connect to daemon and execute status command
826	pub fn execute_status(&self, _service:Option<String>) -> Result<StatusResponse, String> {
827		// In production, this would connect via gRPC or Unix socket
828		// For now, simulate a response
829		Ok(StatusResponse {
830			daemon_running:true,
831			uptime_secs:3600,
832			version:"0.1.0".to_string(),
833			services:self.get_mock_services(),
834			timestamp:Utc::now().to_rfc3339(),
835		})
836	}
837
838	/// Connect to daemon and execute restart command
839	pub fn execute_restart(&self, service:Option<String>, force:bool) -> Result<String, String> {
840		Ok(if let Some(s) = service {
841			format!("Service {} restarted (force: {})", s, force)
842		} else {
843			format!("All services restarted (force: {})", force)
844		})
845	}
846
847	/// Connect to daemon and execute config get command
848	pub fn execute_config_get(&self, key:&str) -> Result<ConfigResponse, String> {
849		Ok(ConfigResponse {
850			key:Some(key.to_string()),
851			value:serde_json::json!("example_value"),
852			path:"/Air/config.json".to_string(),
853			modified:Utc::now().to_rfc3339(),
854		})
855	}
856
857	/// Connect to daemon and execute config set command
858	pub fn execute_config_set(&self, key:&str, value:&str) -> Result<String, String> {
859		Ok(format!("Configuration updated: {} = {}", key, value))
860	}
861
862	/// Connect to daemon and execute config reload command
863	pub fn execute_config_reload(&self, validate:bool) -> Result<String, String> {
864		Ok(format!("Configuration reloaded (validate: {})", validate))
865	}
866
867	/// Connect to daemon and execute config show command
868	pub fn execute_config_show(&self) -> Result<serde_json::Value, String> {
869		Ok(serde_json::json!({
870			"grpc": {
871				"bind_address": "[::1]:50053",
872				"max_connections": 100
873			},
874			"updates": {
875				"auto_download": true,
876				"auto_install": false
877			}
878		}))
879	}
880
881	/// Connect to daemon and execute config validate command
882	pub fn execute_config_validate(&self, _path:Option<String>) -> Result<bool, String> { Ok(true) }
883
884	/// Connect to daemon and execute metrics command
885	pub fn execute_metrics(&self, _service:Option<String>) -> Result<MetricsResponse, String> {
886		Ok(MetricsResponse {
887			timestamp:Utc::now().to_rfc3339(),
888			memory_used_mb:512.0,
889			memory_available_mb:4096.0,
890			cpu_usage_percent:15.5,
891			disk_used_mb:1024,
892			disk_available_mb:51200,
893			active_connections:5,
894			processed_requests:1000,
895			failed_requests:2,
896			service_metrics:self.get_mock_service_metrics(),
897		})
898	}
899
900	/// Connect to daemon and execute logs command
901	pub fn execute_logs(
902		&self,
903		service:Option<String>,
904		_tail:Option<usize>,
905		_filter:Option<String>,
906	) -> Result<Vec<LogEntry>, String> {
907		// Return mock logs
908		Ok(vec![LogEntry {
909			timestamp:Utc::now(),
910			level:"INFO".to_string(),
911			service:service.clone(),
912			message:"Daemon started successfully".to_string(),
913			context:None,
914		}])
915	}
916
917	/// Connect to daemon and execute debug dump-state command
918	pub fn execute_debug_dump_state(&self, _service:Option<String>) -> Result<DaemonState, String> {
919		Ok(DaemonState {
920			timestamp:Utc::now(),
921			version:"0.1.0".to_string(),
922			uptime_secs:3600,
923			services:HashMap::new(),
924			connections:vec![],
925			plugin_state:serde_json::json!({}),
926		})
927	}
928
929	/// Connect to daemon and execute debug dump-connections command
930	pub fn execute_debug_dump_connections(&self) -> Result<Vec<ConnectionInfo>, String> { Ok(vec![]) }
931
932	/// Connect to daemon and execute debug health-check command
933	pub fn execute_debug_health_check(&self, _service:Option<String>) -> Result<HealthCheckResponse, String> {
934		Ok(HealthCheckResponse {
935			overall_healthy:true,
936			overall_health_percentage:100.0,
937			services:HashMap::new(),
938			timestamp:Utc::now().to_rfc3339(),
939		})
940	}
941
942	/// Connect to daemon and execute debug diagnostics command
943	pub fn execute_debug_diagnostics(&self, level:DiagnosticLevel) -> Result<serde_json::Value, String> {
944		Ok(serde_json::json!({
945			"level": format!("{:?}", level),
946			"timestamp": Utc::now().to_rfc3339(),
947			"checks": {
948				"memory": "ok",
949				"cpu": "ok",
950				"disk": "ok"
951			}
952		}))
953	}
954
955	/// Check if daemon is running
956	pub fn is_daemon_running(&self) -> bool {
957		// In production, check via socket connection or process check
958		true
959	}
960
961	/// Get mock services for testing
962	fn get_mock_services(&self) -> HashMap<String, ServiceStatus> {
963		let mut services = HashMap::new();
964		services.insert(
965			"authentication".to_string(),
966			ServiceStatus {
967				name:"authentication".to_string(),
968				running:true,
969				health:ServiceHealth::Healthy,
970				uptime_secs:3600,
971				error:None,
972			},
973		);
974		services.insert(
975			"updates".to_string(),
976			ServiceStatus {
977				name:"updates".to_string(),
978				running:true,
979				health:ServiceHealth::Healthy,
980				uptime_secs:3600,
981				error:None,
982			},
983		);
984		services.insert(
985			"plugins".to_string(),
986			ServiceStatus {
987				name:"plugins".to_string(),
988				running:true,
989				health:ServiceHealth::Healthy,
990				uptime_secs:3600,
991				error:None,
992			},
993		);
994		services
995	}
996
997	/// Get mock service metrics for testing
998	fn get_mock_service_metrics(&self) -> HashMap<String, ServiceMetrics> {
999		let mut metrics = HashMap::new();
1000		metrics.insert(
1001			"authentication".to_string(),
1002			ServiceMetrics {
1003				name:"authentication".to_string(),
1004				requests_total:500,
1005				requests_success:498,
1006				requests_failed:2,
1007				average_latency_ms:12.5,
1008				p99_latency_ms:45.0,
1009			},
1010		);
1011		metrics.insert(
1012			"updates".to_string(),
1013			ServiceMetrics {
1014				name:"updates".to_string(),
1015				requests_total:300,
1016				requests_success:300,
1017				requests_failed:0,
1018				average_latency_ms:25.0,
1019				p99_latency_ms:100.0,
1020			},
1021		);
1022		metrics
1023	}
1024}
1025
1026// =============================================================================
1027// CLI Command Handler
1028// =============================================================================
1029
1030/// Main CLI command handler
1031pub struct CliHandler {
1032	client:DaemonClient,
1033	output_format:OutputFormat,
1034}
1035
1036impl CliHandler {
1037	/// Create a new CLI handler
1038	pub fn new() -> Self {
1039		Self {
1040			client:DaemonClient::new("[::1]:50053".to_string()),
1041			output_format:OutputFormat::Plain,
1042		}
1043	}
1044
1045	/// Create a new CLI handler with custom client
1046	pub fn with_client(client:DaemonClient) -> Self { Self { client, output_format:OutputFormat::Plain } }
1047
1048	/// Set output format
1049	pub fn set_output_format(&mut self, format:OutputFormat) { self.output_format = format; }
1050
1051	/// Check and enforce permission requirements
1052	fn check_permission(&self, command:&Command) -> Result<(), String> {
1053		let required = Self::get_permission_level(command);
1054
1055		if required == PermissionLevel::Admin {
1056			// In production, check for elevated privileges
1057			// For now, we'll just log a warning
1058			log::warn!("Admin privileges required for command");
1059		}
1060
1061		Ok(())
1062	}
1063
1064	/// Get permission level required for a command
1065	fn get_permission_level(command:&Command) -> PermissionLevel {
1066		match command {
1067			Command::Config(ConfigCommand::Set { .. }) => PermissionLevel::Admin,
1068			Command::Config(ConfigCommand::Reload { .. }) => PermissionLevel::Admin,
1069			Command::Restart { force, .. } if *force => PermissionLevel::Admin,
1070			Command::Restart { .. } => PermissionLevel::Admin,
1071			_ => PermissionLevel::User,
1072		}
1073	}
1074
1075	/// Execute a command and return formatted output
1076	pub fn execute(&mut self, command:Command) -> Result<String, String> {
1077		// Check permissions
1078		self.check_permission(&command)?;
1079
1080		match command {
1081			Command::Status { service, verbose, json } => self.handle_status(service, verbose, json),
1082			Command::Restart { service, force } => self.handle_restart(service, force),
1083			Command::Config(config_cmd) => self.handle_config(config_cmd),
1084			Command::Metrics { json, service } => self.handle_metrics(json, service),
1085			Command::Logs { service, tail, filter, follow } => self.handle_logs(service, tail, filter, follow),
1086			Command::Debug(debug_cmd) => self.handle_debug(debug_cmd),
1087			Command::Help { command } => Ok(OutputFormatter::format_help(command.as_deref(), "0.1.0")),
1088			Command::Version => Ok("Air 🪁 v0.1.0".to_string()),
1089		}
1090	}
1091
1092	/// Handle status command
1093	fn handle_status(&self, service:Option<String>, verbose:bool, json:bool) -> Result<String, String> {
1094		let response = self.client.execute_status(service)?;
1095		Ok(OutputFormatter::format_status(&response, verbose, json))
1096	}
1097
1098	/// Handle restart command
1099	fn handle_restart(&self, service:Option<String>, force:bool) -> Result<String, String> {
1100		let result = self.client.execute_restart(service, force)?;
1101		Ok(result)
1102	}
1103
1104	/// Handle config commands
1105	fn handle_config(&self, cmd:ConfigCommand) -> Result<String, String> {
1106		match cmd {
1107			ConfigCommand::Get { key } => {
1108				let response = self.client.execute_config_get(&key)?;
1109				Ok(format!("{} = {}", response.key.unwrap_or_default(), response.value))
1110			},
1111			ConfigCommand::Set { key, value } => {
1112				let result = self.client.execute_config_set(&key, &value)?;
1113				Ok(result)
1114			},
1115			ConfigCommand::Reload { validate } => {
1116				let result = self.client.execute_config_reload(validate)?;
1117				Ok(result)
1118			},
1119			ConfigCommand::Show { json } => {
1120				let config = self.client.execute_config_show()?;
1121				if json {
1122					Ok(serde_json::to_string_pretty(&config).unwrap_or_else(|_| "{}".to_string()))
1123				} else {
1124					Ok(serde_json::to_string_pretty(&config).unwrap_or_else(|_| "{}".to_string()))
1125				}
1126			},
1127			ConfigCommand::Validate { path } => {
1128				let valid = self.client.execute_config_validate(path)?;
1129				if valid {
1130					Ok("Configuration is valid".to_string())
1131				} else {
1132					Err("Configuration validation failed".to_string())
1133				}
1134			},
1135		}
1136	}
1137
1138	/// Handle metrics command
1139	fn handle_metrics(&self, json:bool, service:Option<String>) -> Result<String, String> {
1140		let response = self.client.execute_metrics(service)?;
1141		Ok(OutputFormatter::format_metrics(&response, json))
1142	}
1143
1144	/// Handle logs command
1145	fn handle_logs(
1146		&self,
1147		service:Option<String>,
1148		tail:Option<usize>,
1149		filter:Option<String>,
1150		follow:bool,
1151	) -> Result<String, String> {
1152		let logs = self.client.execute_logs(service, tail, filter)?;
1153
1154		let mut output = String::new();
1155		for entry in logs {
1156			output.push_str(&format!(
1157				"[{}] {} - {}\n",
1158				entry.timestamp.format("%Y-%m-%d %H:%M:%S"),
1159				entry.level,
1160				entry.message
1161			));
1162		}
1163
1164		if follow {
1165			output.push_str("\nFollowing logs (press Ctrl+C to stop)...\n");
1166		}
1167
1168		Ok(output)
1169	}
1170
1171	/// Handle debug commands
1172	fn handle_debug(&self, cmd:DebugCommand) -> Result<String, String> {
1173		match cmd {
1174			DebugCommand::DumpState { service, json } => {
1175				let state = self.client.execute_debug_dump_state(service)?;
1176				if json {
1177					Ok(serde_json::to_string_pretty(&state).unwrap_or_else(|_| "{}".to_string()))
1178				} else {
1179					Ok(format!(
1180						"Daemon State Dump\nVersion: {}\nUptime: {}s\n",
1181						state.version, state.uptime_secs
1182					))
1183				}
1184			},
1185			DebugCommand::DumpConnections { format: _ } => {
1186				let connections = self.client.execute_debug_dump_connections()?;
1187				Ok(format!("Active connections: {}", connections.len()))
1188			},
1189			DebugCommand::HealthCheck { verbose: _, service } => {
1190				let health = self.client.execute_debug_health_check(service)?;
1191				Ok(format!(
1192					"Overall Health: {} ({}%)\n",
1193					if health.overall_healthy { "Healthy" } else { "Unhealthy" },
1194					health.overall_health_percentage
1195				))
1196			},
1197			DebugCommand::Diagnostics { level } => {
1198				let diagnostics = self.client.execute_debug_diagnostics(level)?;
1199				Ok(serde_json::to_string_pretty(&diagnostics).unwrap_or_else(|_| "{}".to_string()))
1200			},
1201		}
1202	}
1203}
1204
1205/// Output format
1206#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1207pub enum OutputFormat {
1208	Plain,
1209	Table,
1210	Json,
1211}
1212
1213// =============================================================================
1214// Help Messages
1215// =============================================================================
1216
1217pub const HELP_MAIN:&str = r#"
1218Air 🪁 - Background Daemon for Land Code Editor
1219Version: {version}
1220
1221USAGE:
1222    Air [COMMAND] [OPTIONS]
1223
1224COMMANDS:
1225    status           Show daemon and service status
1226    restart          Restart services
1227    config           Manage configuration
1228    metrics          View performance metrics
1229    logs             View daemon logs
1230    debug            Debug and diagnostics
1231    help             Show help information
1232    version          Show version information
1233
1234OPTIONS:
1235    -h, --help       Show help
1236    -v, --version    Show version
1237
1238EXAMPLES:
1239    Air status --verbose
1240    Air config get grpc.bind_address
1241    Air metrics --json
1242    Air logs --tail=100 --follow
1243    Air debug health-check
1244
1245Use 'Air help <command>' for more information about a command.
1246"#;
1247
1248pub const HELP_STATUS:&str = r#"
1249Show daemon and service status
1250
1251USAGE:
1252    Air status [OPTIONS]
1253
1254OPTIONS:
1255    -s, --service <NAME>    Show status of specific service
1256    -v, --verbose           Show detailed information
1257    --json                  Output in JSON format
1258
1259EXAMPLES:
1260    Air status
1261    Air status --service authentication --verbose
1262    Air status --json
1263"#;
1264
1265pub const HELP_RESTART:&str = r#"
1266Restart services
1267
1268USAGE:
1269    Air restart [OPTIONS]
1270
1271OPTIONS:
1272    -s, --service <NAME>    Restart specific service
1273    -f, --force             Force restart without graceful shutdown
1274
1275EXAMPLES:
1276    Air restart
1277    Air restart --service updates
1278    Air restart --force
1279"#;
1280
1281pub const HELP_CONFIG:&str = r#"
1282Manage configuration
1283
1284USAGE:
1285    Air config <SUBCOMMAND> [OPTIONS]
1286
1287SUBCOMMANDS:
1288    get <KEY>               Get configuration value
1289    set <KEY> <VALUE>       Set configuration value
1290    reload                  Reload configuration from file
1291    show                    Show current configuration
1292    validate [PATH]         Validate configuration file
1293
1294OPTIONS:
1295    --json                  Output in JSON format
1296    --validate              Validate before reloading
1297
1298EXAMPLES:
1299    Air config get grpc.bind_address
1300    Air config set updates.auto_download true
1301    Air config reload --validate
1302    Air config show --json
1303"#;
1304
1305pub const HELP_METRICS:&str = r#"
1306View performance metrics
1307
1308USAGE:
1309    Air metrics [OPTIONS]
1310
1311OPTIONS:
1312    -s, --service <NAME>    Show metrics for specific service
1313    --json                  Output in JSON format
1314
1315EXAMPLES:
1316    Air metrics
1317    Air metrics --service downloader
1318    Air metrics --json
1319"#;
1320
1321pub const HELP_LOGS:&str = r#"
1322View daemon logs
1323
1324USAGE:
1325    Air logs [OPTIONS]
1326
1327OPTIONS:
1328    -s, --service <NAME>    Show logs from specific service
1329    -n, --tail <N>          Show last N lines (default: 50)
1330    -f, --filter <PATTERN>  Filter logs by pattern
1331    --follow                Follow logs in real-time
1332
1333EXAMPLES:
1334    Air logs
1335    Air logs --service updates --tail=100
1336    Air logs --filter "ERROR" --follow
1337"#;
1338
1339pub const HELP_DEBUG:&str = r#"
1340Debug and diagnostics
1341
1342USAGE:
1343    Air debug <SUBCOMMAND> [OPTIONS]
1344
1345SUBCOMMANDS:
1346    dump-state              Dump current daemon state
1347    dump-connections        Dump active connections
1348    health-check            Perform health check
1349    diagnostics             Run diagnostics
1350
1351OPTIONS:
1352    --json                  Output in JSON format
1353    --verbose               Show detailed information
1354    --service <NAME>        Target specific service
1355    --full                  Full diagnostic level
1356
1357EXAMPLES:
1358    Air debug dump-state
1359    Air debug dump-connections --json
1360    Air debug health-check --verbose
1361    Air debug diagnostics --full
1362"#;
1363
1364// =============================================================================
1365// Output Formatting
1366// =============================================================================
1367
1368/// Format output based on command options
1369pub struct OutputFormatter;
1370
1371impl OutputFormatter {
1372	/// Format status output
1373	pub fn format_status(response:&StatusResponse, verbose:bool, json:bool) -> String {
1374		if json {
1375			serde_json::to_string_pretty(response).unwrap_or_else(|_| "{}".to_string())
1376		} else if verbose {
1377			Self::format_status_verbose(response)
1378		} else {
1379			Self::format_status_compact(response)
1380		}
1381	}
1382
1383	fn format_status_compact(response:&StatusResponse) -> String {
1384		let daemon_status = if response.daemon_running { "🟢 Running" } else { "šŸ”“ Stopped" };
1385
1386		let mut output = format!(
1387			"Air Daemon {}\nVersion: {}\nUptime: {}s\n\nServices:\n",
1388			daemon_status, response.version, response.uptime_secs
1389		);
1390
1391		for (name, status) in &response.services {
1392			let health_symbol = match status.health {
1393				ServiceHealth::Healthy => "🟢",
1394				ServiceHealth::Degraded => "🟔",
1395				ServiceHealth::Unhealthy => "šŸ”“",
1396				ServiceHealth::Unknown => "⚪",
1397			};
1398
1399			output.push_str(&format!(
1400				"  {} {} - {} (uptime: {}s)\n",
1401				health_symbol,
1402				name,
1403				if status.running { "Running" } else { "Stopped" },
1404				status.uptime_secs
1405			));
1406		}
1407
1408		output
1409	}
1410
1411	fn format_status_verbose(response:&StatusResponse) -> String {
1412		let mut output = format!(
1413			"╔════════════════════════════════════════╗\nā•‘ Air Daemon \
1414			 Status\n╠════════════════════════════════════════╣\nā•‘ Status:   {}\nā•‘ Version:  {}\nā•‘ Uptime:   {} \
1415			 seconds\nā•‘ Time:     {}\n╠════════════════════════════════════════╣\n",
1416			if response.daemon_running { "Running" } else { "Stopped" },
1417			response.version,
1418			response.uptime_secs,
1419			response.timestamp
1420		);
1421
1422		output.push_str("ā•‘ Services:\n");
1423		for (name, status) in &response.services {
1424			let health_text = match status.health {
1425				ServiceHealth::Healthy => "Healthy",
1426				ServiceHealth::Degraded => "Degraded",
1427				ServiceHealth::Unhealthy => "Unhealthy",
1428				ServiceHealth::Unknown => "Unknown",
1429			};
1430
1431			output.push_str(&format!(
1432				"ā•‘   • {} ({})\nā•‘     Status: {}\nā•‘     Health: {}\nā•‘     Uptime: {} seconds\n",
1433				name,
1434				if status.running { "running" } else { "stopped" },
1435				if status.running { "Active" } else { "Inactive" },
1436				health_text,
1437				status.uptime_secs
1438			));
1439
1440			if let Some(error) = &status.error {
1441				output.push_str(&format!("ā•‘     Error: {}\n", error));
1442			}
1443		}
1444
1445		output.push_str("ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•\n");
1446		output
1447	}
1448
1449	/// Format metrics output
1450	pub fn format_metrics(response:&MetricsResponse, json:bool) -> String {
1451		if json {
1452			serde_json::to_string_pretty(response).unwrap_or_else(|_| "{}".to_string())
1453		} else {
1454			Self::format_metrics_human(response)
1455		}
1456	}
1457
1458	fn format_metrics_human(response:&MetricsResponse) -> String {
1459		format!(
1460			"╔════════════════════════════════════════╗\nā•‘ Air Daemon \
1461			 Metrics\n╠════════════════════════════════════════╣\nā•‘ Memory:     {:.1}MB / {:.1}MB\nā•‘ CPU:        \
1462			 {:.1}%\nā•‘ Disk:       {}MB / {}MB\nā•‘ Connections: {}\nā•‘ Requests:   {} success, {} \
1463			 failed\nā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•\n",
1464			response.memory_used_mb,
1465			response.memory_available_mb,
1466			response.cpu_usage_percent,
1467			response.disk_used_mb,
1468			response.disk_available_mb,
1469			response.active_connections,
1470			response.processed_requests,
1471			response.failed_requests
1472		)
1473	}
1474
1475	/// Format help message
1476	pub fn format_help(topic:Option<&str>, version:&str) -> String {
1477		match topic {
1478			None => HELP_MAIN.replace("{version}", version),
1479			Some("status") => HELP_STATUS.to_string(),
1480			Some("restart") => HELP_RESTART.to_string(),
1481			Some("config") => HELP_CONFIG.to_string(),
1482			Some("metrics") => HELP_METRICS.to_string(),
1483			Some("logs") => HELP_LOGS.to_string(),
1484			Some("debug") => HELP_DEBUG.to_string(),
1485			_ => {
1486				format!(
1487					"Unknown help topic: {}\n\nUse 'Air help' for general help.",
1488					topic.unwrap_or("unknown")
1489				)
1490			},
1491		}
1492	}
1493}
1494
1495#[cfg(test)]
1496mod tests {
1497	use super::*;
1498
1499	#[test]
1500	fn test_parse_status_command() {
1501		let args = vec!["Air".to_string(), "status".to_string(), "--verbose".to_string()];
1502		let cmd = CliParser::parse(args).unwrap();
1503		if let Command::Status { service, verbose, json } = cmd {
1504			assert!(verbose);
1505			assert!(!json);
1506			assert!(service.is_none());
1507		} else {
1508			panic!("Expected Status command");
1509		}
1510	}
1511
1512	#[test]
1513	fn test_parse_config_set() {
1514		let args = vec![
1515			"Air".to_string(),
1516			"config".to_string(),
1517			"set".to_string(),
1518			"grpc.bind_address".to_string(),
1519			"[::1]:50053".to_string(),
1520		];
1521		let cmd = CliParser::parse(args).unwrap();
1522		if let Command::Config(ConfigCommand::Set { key, value }) = cmd {
1523			assert_eq!(key, "grpc.bind_address");
1524			assert_eq!(value, "[::1]:50053");
1525		} else {
1526			panic!("Expected Config Set command");
1527		}
1528	}
1529}