use crate::Result;
use crate::daemon_id::DaemonId;
use crate::daemon_list::get_all_daemons;
use crate::daemon_status::DaemonStatus;
use crate::ipc::client::IpcClient;
use crate::pitchfork_toml::PitchforkToml;
use crate::settings::settings;
use crate::ui::table::print_table;
use comfy_table::{Cell, Color, ContentArrangement, Table};
#[derive(Debug, clap::Args)]
#[clap(
visible_alias = "ls",
verbatim_doc_comment,
long_about = "\
List all daemons
Displays a table of all tracked daemons with their PIDs, status,
whether they are disabled, and any error messages.
This command shows both:
- Active daemons (currently running or stopped)
- Available daemons (defined in config but not yet started)
Example:
pitchfork list
pitchfork ls Alias for 'list'
pitchfork list --hide-header Output without column headers
Output:
Name PID Status Error
api 12345 running
worker available
db errored exit code 127"
)]
pub struct List {
#[clap(long)]
hide_header: bool,
}
impl List {
pub async fn run(&self) -> Result<()> {
let client = IpcClient::connect(true).await?;
let s = settings();
let mut table = Table::new();
table
.load_preset(comfy_table::presets::NOTHING)
.set_content_arrangement(ContentArrangement::Dynamic);
if !self.hide_header && console::user_attended() {
if s.proxy.enable {
table.set_header(vec!["Name", "PID", "Status", "", "Proxy URL", "Error"]);
} else {
table.set_header(vec!["Name", "PID", "Status", "", "Error"]);
}
}
let entries = get_all_daemons(&client).await?;
let global_slugs = PitchforkToml::read_global_slugs();
let all_ids: Vec<DaemonId> = entries.iter().map(|e| e.id.clone()).collect();
for entry in entries {
let display_name = entry.id.styled_display_name(Some(all_ids.iter()));
let status_text = if entry.is_available {
"available".to_string()
} else {
entry.daemon.status.to_string()
};
let status_color = if entry.is_available {
Color::Cyan
} else {
match entry.daemon.status {
DaemonStatus::Failed(_) => Color::Red,
DaemonStatus::Waiting => Color::Yellow,
DaemonStatus::Running => Color::Green,
DaemonStatus::Stopping => Color::Yellow,
DaemonStatus::Stopped => Color::DarkGrey,
DaemonStatus::Errored(_) => Color::Red,
}
};
let disabled_marker = if entry.is_disabled { "disabled" } else { "" };
let error_msg = entry.daemon.status.error_message().unwrap_or_default();
let error_cell = if error_msg.is_empty() {
Cell::new("")
} else {
Cell::new(&error_msg).fg(Color::Red)
};
let pid_str = entry.daemon.pid.map(|p| p.to_string()).unwrap_or_default();
let mut row = vec![
Cell::new(&display_name),
Cell::new(pid_str),
Cell::new(&status_text).fg(status_color),
Cell::new(disabled_marker),
];
if s.proxy.enable {
let slug = global_slugs
.iter()
.find(|(slug, se)| {
let daemon_name = se.daemon.as_deref().unwrap_or(slug);
if entry.id.name() != daemon_name {
return false;
}
match PitchforkToml::namespace_for_dir(&se.dir) {
Ok(ns) => entry.id.namespace() == ns,
Err(_) => true, }
})
.map(|(slug, _)| slug.as_str());
let proxy_cell = match build_proxy_url(slug, s) {
Some(proxy_url)
if entry.daemon.active_port.is_some()
|| !entry.daemon.resolved_port.is_empty() =>
{
Cell::new(&proxy_url).fg(Color::Cyan)
}
_ => Cell::new(""), };
row.push(proxy_cell);
}
row.push(error_cell);
table.add_row(row);
}
print_table(table)
}
}
pub fn build_proxy_url(slug: Option<&str>, s: &crate::settings::Settings) -> Option<String> {
let slug = slug?;
let scheme = if s.proxy.https { "https" } else { "http" };
let tld = &s.proxy.tld;
let standard_port = if s.proxy.https { 443u16 } else { 80u16 };
let effective_port = u16::try_from(s.proxy.port).ok().filter(|&p| p > 0)?;
let host = format!("{slug}.{tld}");
Some(if effective_port == standard_port {
format!("{scheme}://{host}")
} else {
format!("{scheme}://{host}:{effective_port}")
})
}