Skip to main content

systemprompt_cli/commands/admin/agents/
logs_disk.rs

1use std::path::Path;
2use std::process::Command;
3
4use anyhow::{Context, Result, anyhow};
5
6use super::logs::LogsArgs;
7use super::types::AgentLogsOutput;
8use crate::CliConfig;
9use crate::interactive::{Prompter, resolve_required};
10use crate::shared::CommandOutput;
11use crate::shared::disk_logs::{display_names, find_log_file, list_log_files, read_log_lines};
12use systemprompt_models::artifacts::ListItem;
13
14pub(super) fn execute_disk_mode(
15    args: &LogsArgs,
16    prompter: &dyn Prompter,
17    config: &CliConfig,
18    logs_path: &Path,
19) -> Result<CommandOutput> {
20    if !logs_path.exists() {
21        return Err(anyhow!(
22            "Logs directory does not exist: {}",
23            logs_path.display()
24        ));
25    }
26
27    if args.agent.is_none() && !config.is_interactive() {
28        let log_files = list_log_files(logs_path, "agent-")?;
29        let output = AgentLogsOutput {
30            agent: None,
31            source: "disk".to_owned(),
32            logs: vec![],
33            log_files,
34        };
35        let items: Vec<ListItem> = output
36            .log_files
37            .iter()
38            .map(|file| ListItem::new(file.clone(), String::new(), file.clone()))
39            .collect();
40        return Ok(CommandOutput::list(items).with_title("Available Agent Log Files"));
41    }
42
43    let agent = resolve_required(args.agent.clone(), "agent", config, || {
44        prompt_log_selection(prompter, logs_path)
45    })?;
46
47    let log_file = find_log_file(logs_path, "agent-", &agent)?;
48    let logs = read_log_lines(&log_file, args.lines, |_| true)?;
49
50    Ok(CommandOutput::card_value(
51        format!("Agent Logs (Disk): {}", agent),
52        &AgentLogsOutput {
53            agent: Some(agent),
54            source: "disk".to_owned(),
55            logs,
56            log_files: vec![],
57        },
58    ))
59}
60
61pub(super) fn execute_follow_mode(
62    args: &LogsArgs,
63    prompter: &dyn Prompter,
64    config: &CliConfig,
65    logs_path: &Path,
66) -> Result<CommandOutput> {
67    if !logs_path.exists() {
68        return Err(anyhow!(
69            "Logs directory does not exist: {}",
70            logs_path.display()
71        ));
72    }
73
74    let agent = resolve_required(args.agent.clone(), "agent", config, || {
75        prompt_log_selection(prompter, logs_path)
76    })?;
77
78    let log_file = find_log_file(logs_path, "agent-", &agent)?;
79
80    let status = Command::new("tail")
81        .arg("-f")
82        .arg(&log_file)
83        .status()
84        .context("Failed to execute tail -f")?;
85
86    if !status.success() {
87        return Err(anyhow!("tail -f exited with non-zero status"));
88    }
89
90    Ok(CommandOutput::card_value(
91        "Agent Logs",
92        &AgentLogsOutput {
93            agent: Some(agent),
94            source: "disk".to_owned(),
95            logs: vec![],
96            log_files: vec![],
97        },
98    ))
99}
100
101fn prompt_log_selection(prompter: &dyn Prompter, logs_dir: &Path) -> Result<String> {
102    let log_files = list_log_files(logs_dir, "agent-")?;
103    select_agent_from_log_files(prompter, &log_files, logs_dir)
104}
105
106pub fn select_agent_from_log_files(
107    prompter: &dyn Prompter,
108    log_files: &[String],
109    logs_dir: &Path,
110) -> Result<String> {
111    if log_files.is_empty() {
112        return Err(anyhow!(
113            "No agent log files found in {}",
114            logs_dir.display()
115        ));
116    }
117
118    let agents = display_names(log_files, "agent-");
119
120    let selection = prompter
121        .select("Select agent logs to view", &agents)
122        .context("Failed to get agent selection")?;
123
124    Ok(agents[selection].clone())
125}