From f6543fa0f0f12276e1b34d66e5dc3b68e470b54e Mon Sep 17 00:00:00 2001 From: Justyn <11221796+justyn-clark@users.noreply.github.com> Date: Fri, 3 Apr 2026 00:19:01 -0700 Subject: [PATCH] Fix pre-release CLI startup and config handling --- src/config.rs | 2 +- src/lib.rs | 37 ++++++++++++++++++++++++++----------- src/proc/kill.rs | 5 ++++- src/scan/lsof.rs | 6 +----- src/scan/mod.rs | 4 +++- src/scan/ss.rs | 4 +--- src/tui/mod.rs | 9 +++++++-- src/tui/ui.rs | 5 +++-- 8 files changed, 46 insertions(+), 26 deletions(-) diff --git a/src/config.rs b/src/config.rs index e3d2b48..ca2708a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -2,7 +2,7 @@ use std::collections::{BTreeMap, HashMap, HashSet}; use std::fs; use std::path::{Path, PathBuf}; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result, bail}; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Deserialize, Serialize)] diff --git a/src/lib.rs b/src/lib.rs index 5c98164..c713b08 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,7 @@ use std::path::Path; use std::process::Command; use std::time::Duration; -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result, bail}; use cli::{Commands, ConfigCommands}; use config::{Config, ServiceConfig}; use scan::model::{JoinedPortRecord, ScanRecord}; @@ -26,7 +26,7 @@ pub fn execute(cli: cli::Cli) -> Result<()> { render::json::print_pretty(&records)?; } Commands::List => { - let cfg = Config::load(&config_path)?; + let cfg = load_config_or_empty(&config_path)?; let joined = join_records(&scan::scan_listeners()?, &cfg); if cli.json { render::json::print_pretty(&joined)?; @@ -35,7 +35,7 @@ pub fn execute(cli: cli::Cli) -> Result<()> { } } Commands::Tui => { - let cfg = Config::load(&config_path)?; + let cfg = load_config_or_empty(&config_path)?; tui::run_tui(config_path.as_path(), cfg)?; } Commands::Kill { @@ -58,7 +58,7 @@ pub fn execute(cli: cli::Cli) -> Result<()> { start_service(svc)?; } Commands::Doctor => { - let cfg = Config::load(&config_path)?; + let cfg = load_config_or_empty(&config_path)?; let report = config::doctor::doctor(&cfg, &scan::scan_listeners()?); if cli.json { render::json::print_pretty(&report)?; @@ -70,14 +70,20 @@ pub fn execute(cli: cli::Cli) -> Result<()> { } } Commands::Urls { host } => { - let cfg = Config::load(&config_path)?; + let cfg = load_config_or_empty(&config_path)?; let records = scan::scan_listeners()?; let hostname = host.unwrap_or_else(|| { - hostname::get() + let name = hostname::get() .unwrap_or_default() .to_string_lossy() - .to_string() + .to_string(); + + if name.ends_with(".local") { + name + } else { + format!("{name}.local") + } }); let mut services = cfg.services.iter().collect::>(); @@ -148,14 +154,23 @@ fn start_service(service: &ServiceConfig) -> Result<()> { let mut cmd = Command::new("zsh"); cmd.arg("-lc").arg(start).current_dir(&service.repo); - let status = cmd.status().context("failed to execute start command")?; - if !status.success() { - bail!("start command exited with {status}"); - } + cmd.spawn().context("failed to start service")?; + + println!("started {}", service.repo.display()); Ok(()) } +fn load_config_or_empty(path: &Path) -> Result { + if path.exists() { + Config::load(path) + } else { + Ok(Config { + services: Default::default(), + }) + } +} + pub fn open_config(path: &Path) -> Result<()> { let status = if cfg!(target_os = "macos") { Command::new("open").arg(path).status() diff --git a/src/proc/kill.rs b/src/proc/kill.rs index 163913e..04cf63d 100644 --- a/src/proc/kill.rs +++ b/src/proc/kill.rs @@ -56,6 +56,9 @@ fn signal_group(pgid: i32, hard: bool) -> Result<()> { fn signal_pid(pid: i32, hard: bool) -> Result<()> { let sig = if hard { "-KILL" } else { "-TERM" }; - let _ = Command::new("kill").arg(sig).arg(pid.to_string()).status()?; + let _ = Command::new("kill") + .arg(sig) + .arg(pid.to_string()) + .status()?; Ok(()) } diff --git a/src/scan/lsof.rs b/src/scan/lsof.rs index 872078f..6b338ab 100644 --- a/src/scan/lsof.rs +++ b/src/scan/lsof.rs @@ -3,11 +3,7 @@ use anyhow::Result; use super::model::ListenerRecord; pub fn parse_lsof_listeners(input: &str) -> Vec { - input - .lines() - .skip(1) - .filter_map(parse_line) - .collect() + input.lines().skip(1).filter_map(parse_line).collect() } fn parse_line(line: &str) -> Option { diff --git a/src/scan/mod.rs b/src/scan/mod.rs index 59292fa..c94a295 100644 --- a/src/scan/mod.rs +++ b/src/scan/mod.rs @@ -22,7 +22,9 @@ pub fn scan_listeners() -> Result> { pid: listener.pid, ppid: ps.as_ref().map(|v| v.ppid), pgid: ps.as_ref().map(|v| v.pgid), - command: ps.map(|v| v.command).unwrap_or_else(|| "".to_string()), + command: ps + .map(|v| v.command) + .unwrap_or_else(|| "".to_string()), cwd, repo_root, }); diff --git a/src/scan/ss.rs b/src/scan/ss.rs index 6a00ecd..6b5db65 100644 --- a/src/scan/ss.rs +++ b/src/scan/ss.rs @@ -3,9 +3,7 @@ use anyhow::Result; use super::model::ListenerRecord; pub fn run_ss() -> Result> { - let out = std::process::Command::new("ss") - .args(["-lptn"]) - .output()?; + let out = std::process::Command::new("ss").args(["-lptn"]).output()?; if !out.status.success() { return Ok(vec![]); diff --git a/src/tui/mod.rs b/src/tui/mod.rs index 5bc1e41..8f7f826 100644 --- a/src/tui/mod.rs +++ b/src/tui/mod.rs @@ -41,13 +41,18 @@ pub fn run_tui(config_path: &Path, cfg: Config) -> Result<()> { Action::Rescan => app.set_rows(join_with_config(&cfg)?), Action::Term => { if let Some(row) = app.selected() { - let _ = proc::kill::kill_record(&row.record, Duration::from_millis(1500), false); + let _ = proc::kill::kill_record( + &row.record, + Duration::from_millis(1500), + false, + ); app.set_rows(join_with_config(&cfg)?); } } Action::Kill => { if let Some(row) = app.selected() { - let _ = proc::kill::kill_record(&row.record, Duration::from_millis(1), true); + let _ = + proc::kill::kill_record(&row.record, Duration::from_millis(1), true); app.set_rows(join_with_config(&cfg)?); } } diff --git a/src/tui/ui.rs b/src/tui/ui.rs index 88b7acf..845b487 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -52,8 +52,9 @@ pub fn draw(frame: &mut Frame<'_>, app: &App) { frame.render_widget(table, chunks[0]); - let help = Paragraph::new("k term | K kill | s start | r rescan | e open config | / filter | q quit") - .block(Block::default().borders(Borders::ALL)); + let help = + Paragraph::new("k term | K kill | s start | r rescan | e open config | / filter | q quit") + .block(Block::default().borders(Borders::ALL)); frame.render_widget(help, chunks[1]); let filter = Paragraph::new(format!("filter: {}", app.filter));