zinit_client/cli/
commands.rs

1//! CLI command implementations
2
3use crate::cli::args::{Args, Command, ServerAction};
4use crate::client::ZinitClient;
5use anyhow::{Context, Result};
6use colored::Colorize;
7use std::process::Command as ProcessCommand;
8
9/// Run a CLI command
10pub async fn run_command(args: &Args) -> Result<()> {
11    // Handle -i (stdin) flag
12    if args.stdin {
13        return run_stdin().await;
14    }
15
16    // Handle -c (inline script) flag
17    if let Some(script) = &args.inline_script {
18        return run_inline(script).await;
19    }
20
21    match &args.command {
22        Some(Command::Server { action }) => run_server_command(action).await,
23        Some(Command::List) => run_list().await,
24        Some(Command::Status { name }) => run_status(name).await,
25        Some(Command::Start { name }) => run_start(name).await,
26        Some(Command::Stop { name }) => run_stop(name).await,
27        Some(Command::Restart { name }) => run_restart(name).await,
28        Some(Command::Delete { name }) => run_delete(name).await,
29        Some(Command::Kill { name, signal }) => run_kill(name, signal).await,
30        Some(Command::Stats { name }) => run_stats(name).await,
31        Some(Command::Logs {
32            service,
33            follow: _,
34            lines,
35        }) => run_logs(service.as_deref(), *lines).await,
36        Some(Command::Repl) => run_repl().await,
37        Some(Command::Tui) => run_tui().await,
38        Some(Command::Ping) => run_ping().await,
39        None => {
40            if let Some(script) = &args.script {
41                run_script(script).await
42            } else {
43                println!("Use --help for usage information");
44                Ok(())
45            }
46        }
47    }
48}
49
50fn get_client() -> Result<ZinitClient> {
51    ZinitClient::try_default()
52}
53
54async fn run_server_command(action: &ServerAction) -> Result<()> {
55    match action {
56        ServerAction::Start { background, port } => {
57            let mut cmd = ProcessCommand::new("zinit-server");
58            if *background {
59                cmd.arg("-bg");
60            }
61            if let Some(p) = port {
62                cmd.arg("--port").arg(p.to_string());
63            }
64
65            let status = cmd.status().context("Failed to start zinit-server")?;
66            if !status.success() {
67                anyhow::bail!("zinit-server exited with error");
68            }
69            Ok(())
70        }
71        ServerAction::Stop => {
72            let _ = ProcessCommand::new("pkill")
73                .args(["-f", "zinit-server"])
74                .status();
75            println!("Server stopped");
76            Ok(())
77        }
78        ServerAction::Restart => {
79            let _ = ProcessCommand::new("pkill")
80                .args(["-f", "zinit-server"])
81                .status();
82            std::thread::sleep(std::time::Duration::from_millis(500));
83
84            let status = ProcessCommand::new("zinit-server")
85                .arg("-bg")
86                .status()
87                .context("Failed to start zinit-server")?;
88            if !status.success() {
89                anyhow::bail!("zinit-server exited with error");
90            }
91            println!("Server restarted");
92            Ok(())
93        }
94    }
95}
96
97async fn run_list() -> Result<()> {
98    let client = get_client()?;
99    let services = client.list().await?;
100
101    if services.is_empty() {
102        println!("No services registered");
103        return Ok(());
104    }
105
106    println!("{}", "Services:".bold());
107    for name in &services {
108        match client.status(name).await {
109            Ok(status) => {
110                // Check if service was intentionally stopped:
111                // - Exit code 143 = killed by SIGTERM (128+15)
112                // - Exit code 137 = killed by SIGKILL (128+9)
113                // - Signaled with SIGTERM
114                // These are typically xinit-managed services that go to sleep when idle
115                let is_sleeping = status.state.contains("143")
116                    || status.state.contains("137")
117                    || status.state.contains("SIGTERM")
118                    || status.state.contains("SIGKILL");
119
120                let state_colored = if is_sleeping {
121                    "Sleeping".blue()
122                } else {
123                    match status.state.as_str() {
124                        s if s.contains("Running") => status.state.green(),
125                        s if s.contains("Success") => status.state.green(),
126                        s if s.contains("Blocked") => status.state.yellow(),
127                        s if s.contains("Spawned") => status.state.cyan(),
128                        _ => status.state.red(),
129                    }
130                };
131                println!("  {} {} (pid: {})", name.bold(), state_colored, status.pid);
132            }
133            Err(_) => {
134                println!("  {} {}", name.bold(), "unknown".dimmed());
135            }
136        }
137    }
138
139    Ok(())
140}
141
142async fn run_status(name: &str) -> Result<()> {
143    let client = get_client()?;
144    let status = client.status(name).await?;
145
146    println!("{}: {}", "Service".bold(), name);
147    println!("  {}: {}", "State".bold(), status.state);
148    println!("  {}: {}", "Target".bold(), status.target);
149    println!("  {}: {}", "PID".bold(), status.pid);
150
151    Ok(())
152}
153
154async fn run_start(name: &str) -> Result<()> {
155    let client = get_client()?;
156    client.start(name).await?;
157    println!("Started service: {}", name.green());
158    Ok(())
159}
160
161async fn run_stop(name: &str) -> Result<()> {
162    let client = get_client()?;
163    client.stop(name).await?;
164    println!("Stopped service: {}", name.yellow());
165    Ok(())
166}
167
168async fn run_restart(name: &str) -> Result<()> {
169    let client = get_client()?;
170    client.restart(name).await?;
171    println!("Restarted service: {}", name.green());
172    Ok(())
173}
174
175async fn run_delete(name: &str) -> Result<()> {
176    let client = get_client()?;
177    client.delete(name).await?;
178    println!("Deleted service: {}", name.red());
179    Ok(())
180}
181
182async fn run_kill(name: &str, signal: &str) -> Result<()> {
183    let client = get_client()?;
184    client.kill(name, signal).await?;
185    println!("Sent {} to service: {}", signal, name);
186    Ok(())
187}
188
189async fn run_stats(name: &str) -> Result<()> {
190    let client = get_client()?;
191    let stats = client.stats(name).await?;
192
193    println!("{}: {}", "Service".bold(), name);
194    println!("  {}: {}", "PID".bold(), stats.pid);
195    println!(
196        "  {}: {} MB",
197        "Memory".bold(),
198        stats.memory_usage / 1024 / 1024
199    );
200    println!("  {}: {:.1}%", "CPU".bold(), stats.cpu_usage);
201
202    if !stats.children.is_empty() {
203        println!("  {}:", "Children".bold());
204        for child in &stats.children {
205            println!(
206                "    PID {}: {} MB, {:.1}% CPU",
207                child.pid,
208                child.memory_usage / 1024 / 1024,
209                child.cpu_usage
210            );
211        }
212    }
213
214    Ok(())
215}
216
217async fn run_logs(service: Option<&str>, lines: u32) -> Result<()> {
218    let client = get_client()?;
219
220    let logs = if let Some(name) = service {
221        client.logs_filter(name).await?
222    } else {
223        client.logs_tail(lines).await?
224    };
225
226    for line in logs {
227        println!("{}", line);
228    }
229
230    Ok(())
231}
232
233async fn run_script(path: &str) -> Result<()> {
234    use std::path::Path;
235
236    let script_path = Path::new(path);
237
238    // Check if it's a directory - run all .rhai files in it
239    if script_path.is_dir() {
240        let mut scripts: Vec<_> = std::fs::read_dir(script_path)?
241            .filter_map(|e| e.ok())
242            .filter(|e| {
243                e.path()
244                    .extension()
245                    .map(|ext| ext == "rhai")
246                    .unwrap_or(false)
247            })
248            .collect();
249
250        scripts.sort_by_key(|e| e.path());
251
252        if scripts.is_empty() {
253            println!("No .rhai scripts found in {}", path);
254            return Ok(());
255        }
256
257        for entry in scripts {
258            let file_path = entry.path();
259            println!("{} {}", "Running:".green(), file_path.display());
260            match crate::rhai::run_script_file(&file_path) {
261                Ok(result) => {
262                    if !result.is_unit() {
263                        println!("{} {:?}", "=>".green(), result);
264                    }
265                }
266                Err(e) => {
267                    println!("{}: {}", "Error".red(), e);
268                }
269            }
270        }
271    } else {
272        // Single file
273        match crate::rhai::run_script_file(script_path) {
274            Ok(result) => {
275                if !result.is_unit() {
276                    println!("{} {:?}", "=>".green(), result);
277                }
278            }
279            Err(e) => {
280                println!("{}: {}", "Error".red(), e);
281                anyhow::bail!("Script execution failed");
282            }
283        }
284    }
285
286    Ok(())
287}
288
289async fn run_stdin() -> Result<()> {
290    use std::io::Read;
291    let mut script = String::new();
292    std::io::stdin()
293        .read_to_string(&mut script)
294        .context("Failed to read from stdin")?;
295
296    match crate::rhai::run_script(&script) {
297        Ok(result) => {
298            if !result.is_unit() {
299                println!("{} {:?}", "=>".green(), result);
300            }
301            Ok(())
302        }
303        Err(e) => {
304            println!("{}: {}", "Error".red(), e);
305            anyhow::bail!("Script execution failed");
306        }
307    }
308}
309
310async fn run_inline(script: &str) -> Result<()> {
311    match crate::rhai::run_script(script) {
312        Ok(result) => {
313            if !result.is_unit() {
314                println!("{} {:?}", "=>".green(), result);
315            }
316            Ok(())
317        }
318        Err(e) => {
319            println!("{}: {}", "Error".red(), e);
320            anyhow::bail!("Script execution failed");
321        }
322    }
323}
324
325async fn run_repl() -> Result<()> {
326    let handle = crate::client::ZinitHandle::new()?;
327    crate::repl::run_repl(handle)?;
328    Ok(())
329}
330
331async fn run_tui() -> Result<()> {
332    let handle = crate::client::ZinitHandle::new()?;
333    crate::tui::run_tui(handle)?;
334    Ok(())
335}
336
337async fn run_ping() -> Result<()> {
338    let client = get_client()?;
339    let response = client.ping().await?;
340    println!(
341        "{}: {} (version {})",
342        "Server".bold(),
343        response.message.green(),
344        response.version
345    );
346    Ok(())
347}