use crate::profile::load_profile;
use crate::{CliError, CliResult};
use chrono::{SecondsFormat, Utc};
use clap::Args;
use std::fs;
use std::path::PathBuf;
#[derive(Debug, Args)]
pub struct StatusArgs {
#[arg(long)]
pub profile: Option<PathBuf>,
#[arg(
long,
default_value = "outputs",
help = "runtime outputs root; pass <project>/outputs to write <project>/outputs/sessions/..."
)]
pub root: PathBuf,
#[arg(long)]
pub session_id: String,
#[arg(long)]
pub timestamp: Option<String>,
#[arg(long)]
pub phase: String,
#[arg(long)]
pub mode: String,
#[arg(long)]
pub artifact_ref: String,
#[arg(long)]
pub lead_agent: String,
#[arg(long)]
pub status: String,
#[arg(long, help = "completed_since_last_update value.")]
pub completed: String,
#[arg(long)]
pub in_progress: String,
#[arg(long)]
pub next_action: String,
#[arg(long, default_value = "none")]
pub blockers: String,
#[arg(long, default_value = "none")]
pub asks_for_zevs: String,
#[arg(long, default_value = "none")]
pub risk: String,
#[arg(long, default_value = "unknown")]
pub expected_wait: String,
#[arg(long)]
pub event: Option<String>,
#[arg(long, default_value_t = 10)]
pub max_events: usize,
}
pub fn run(args: StatusArgs) -> CliResult<()> {
let profile = load_profile(args.profile.as_deref())?;
if !profile
.operator_interface
.workflow_status_enum
.contains(&args.status)
{
return Err(CliError::usage(format!(
"invalid choice: {:?} (choose from {})",
args.status,
profile.operator_interface.workflow_status_enum.join(", ")
)));
}
if args.max_events < 1 {
return Err(CliError::usage("--max-events must be >= 1"));
}
let timestamp = args
.timestamp
.clone()
.unwrap_or_else(|| Utc::now().to_rfc3339_opts(SecondsFormat::Secs, false));
let status_path = args
.root
.join("sessions")
.join(&args.session_id)
.join("status.md");
let existing_events = read_existing_events(&status_path)?;
let current_event = args
.event
.clone()
.unwrap_or_else(|| format!("status={}; next={}", args.status, args.next_action));
let mut events = vec![format!("{timestamp} - {current_event}")];
events.extend(existing_events);
events.truncate(args.max_events);
if let Some(parent) = status_path.parent() {
fs::create_dir_all(parent).map_err(|error| {
CliError::failure(format!("failed to create {}: {error}", parent.display()))
})?;
}
fs::write(&status_path, render_status(&args, ×tamp, &events)).map_err(|error| {
CliError::failure(format!(
"failed to write {}: {error}",
status_path.display()
))
})?;
println!("{}", status_path.display());
Ok(())
}
fn read_existing_events(path: &PathBuf) -> CliResult<Vec<String>> {
if !path.exists() {
return Ok(Vec::new());
}
let content = fs::read_to_string(path).map_err(|error| {
CliError::failure(format!("failed to read {}: {error}", path.display()))
})?;
let Some((_, events_text)) = content.split_once("## Rolling Events") else {
return Ok(Vec::new());
};
Ok(events_text
.lines()
.filter_map(|line| {
let line = line.trim();
let (prefix, entry) = line.split_once(". ")?;
prefix.parse::<usize>().ok()?;
Some(entry.to_string())
})
.collect())
}
fn render_status(args: &StatusArgs, timestamp: &str, events: &[String]) -> String {
let event_lines = if events.is_empty() {
"No events recorded.".to_string()
} else {
events
.iter()
.enumerate()
.map(|(index, event)| format!("{}. {event}", index + 1))
.collect::<Vec<_>>()
.join("\n")
};
format!(
"# Session Status: {session_id}\n\n\
session_id: {session_id}\n\
last_update: {timestamp}\n\
lead_agent: {lead_agent}\n\
status: {status}\n\n\
## Current State\n\n\
- phase: {phase}\n\
- mode: {mode}\n\
- artifact_ref: {artifact_ref}\n\
- completed_since_last_update: {completed}\n\
- in_progress: {in_progress}\n\
- next_action: {next_action}\n\
- blockers: {blockers}\n\
- asks_for_Zevs: {asks_for_zevs}\n\
- risk_or_residual_uncertainty: {risk}\n\
- expected_wait: {expected_wait}\n\n\
## Rolling Events\n\n\
{event_lines}\n",
session_id = args.session_id,
lead_agent = args.lead_agent,
status = args.status,
phase = args.phase,
mode = args.mode,
artifact_ref = args.artifact_ref,
completed = args.completed,
in_progress = args.in_progress,
next_action = args.next_action,
blockers = args.blockers,
asks_for_zevs = args.asks_for_zevs,
risk = args.risk,
expected_wait = args.expected_wait,
)
}