use crate::{Command, CommandContext, CommandResult};
pub struct StatusCommand;
impl Command for StatusCommand {
fn name(&self) -> &str {
"status"
}
fn aliases(&self) -> &[&str] {
&["info"]
}
fn description(&self) -> &str {
"Show provider, model, tools, hooks, session stats"
}
fn execute(&self, _args: &str, ctx: &mut CommandContext) -> CommandResult {
let total_tokens = ctx.session_input_tokens + ctx.session_output_tokens;
let safety_line = if ctx.nous_scores.is_empty() {
" Safety: (no evaluations yet)".to_string()
} else {
let scores_str: Vec<String> = ctx
.nous_scores
.iter()
.map(|(name, val)| format!("{name}: {val:.2}"))
.collect();
format!(" Safety: {}", scores_str.join(", "))
};
let economic_line = match (&ctx.economic_mode, ctx.budget_usd) {
(Some(mode), Some(budget)) => {
format!(
" Economic: {mode} (${:.4} / ${:.2} budget)",
ctx.session_cost_usd, budget
)
}
(Some(mode), None) => {
format!(" Economic: {mode} (${:.4} spent)", ctx.session_cost_usd)
}
(None, Some(budget)) => {
format!(
" Economic: ${:.4} / ${:.2} budget",
ctx.session_cost_usd, budget
)
}
(None, None) => {
format!(
" Economic: ${:.4} spent (no budget set)",
ctx.session_cost_usd
)
}
};
let workspace_line = match &ctx.workspace_journal_status {
Some(status) => format!(" Workspace: {status}"),
None => " Workspace: (not configured)".to_string(),
};
let identity_line = match (&ctx.identity_tier, &ctx.identity_subject) {
(Some(tier), Some(subject)) => format!(" Identity: {tier} ({subject})"),
(Some(tier), None) => format!(" Identity: {tier}"),
_ => " Identity: anonymous local agent".to_string(),
};
let output = format!(
"Session status:\n\
\n Provider: {}\
\n Model: {}\
\n Tools: {}\
\n Hooks: {}\
\n Skills: {}\
\n Turns: {}\
\n Tokens: {} (in: {}, out: {})\
\n Cost: ${:.4}\
\n{safety_line}\
\n{economic_line}\
\n{identity_line}\
\n{workspace_line}",
ctx.provider_name,
ctx.model_name,
ctx.tools_count,
ctx.hooks_count,
ctx.skill_names.len(),
ctx.session_turns,
total_tokens,
ctx.session_input_tokens,
ctx.session_output_tokens,
ctx.session_cost_usd,
);
CommandResult::Output(output)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn status_shows_provider_and_model() {
let cmd = StatusCommand;
let mut ctx = CommandContext {
provider_name: "anthropic".to_string(),
model_name: "claude-sonnet-4-20250514".to_string(),
tools_count: 10,
hooks_count: 3,
skill_names: vec!["alpha".to_string(), "beta".to_string()],
session_turns: 5,
session_input_tokens: 1000,
session_output_tokens: 500,
session_cost_usd: 0.05,
..Default::default()
};
match cmd.execute("", &mut ctx) {
CommandResult::Output(text) => {
assert!(text.contains("anthropic"));
assert!(text.contains("claude-sonnet-4-20250514"));
assert!(text.contains("Tools: 10"));
assert!(text.contains("Hooks: 3"));
assert!(text.contains("Skills: 2"));
assert!(text.contains("Turns: 5"));
assert!(text.contains("1500"));
assert!(text.contains("Safety:"));
assert!(text.contains("Economic:"));
}
other => panic!("expected Output, got {other:?}"),
}
}
#[test]
fn status_shows_nous_scores() {
let cmd = StatusCommand;
let mut ctx = CommandContext {
nous_scores: vec![
("safety_compliance".to_string(), 0.95),
("tool_correctness".to_string(), 1.0),
],
..Default::default()
};
match cmd.execute("", &mut ctx) {
CommandResult::Output(text) => {
assert!(text.contains("safety_compliance: 0.95"));
assert!(text.contains("tool_correctness: 1.00"));
}
other => panic!("expected Output, got {other:?}"),
}
}
#[test]
fn status_shows_economic_mode_with_budget() {
let cmd = StatusCommand;
let mut ctx = CommandContext {
session_cost_usd: 0.0741,
budget_usd: Some(5.0),
economic_mode: Some("Sovereign".to_string()),
..Default::default()
};
match cmd.execute("", &mut ctx) {
CommandResult::Output(text) => {
assert!(text.contains("Sovereign"));
assert!(text.contains("$0.0741"));
assert!(text.contains("$5.00 budget"));
}
other => panic!("expected Output, got {other:?}"),
}
}
#[test]
fn status_shows_budget_without_autonomic() {
let cmd = StatusCommand;
let mut ctx = CommandContext {
session_cost_usd: 1.25,
budget_usd: Some(10.0),
..Default::default()
};
match cmd.execute("", &mut ctx) {
CommandResult::Output(text) => {
assert!(text.contains("$1.25"));
assert!(text.contains("$10.00 budget"));
assert!(!text.contains("Sovereign"));
}
other => panic!("expected Output, got {other:?}"),
}
}
#[test]
fn status_shows_identity_with_subject() {
let cmd = StatusCommand;
let mut ctx = CommandContext {
identity_tier: Some("pro".to_string()),
identity_subject: Some("user@example.com".to_string()),
..Default::default()
};
match cmd.execute("", &mut ctx) {
CommandResult::Output(text) => {
assert!(text.contains("Identity: pro (user@example.com)"));
}
other => panic!("expected Output, got {other:?}"),
}
}
#[test]
fn status_shows_anonymous_identity() {
let cmd = StatusCommand;
let mut ctx = CommandContext::default();
match cmd.execute("", &mut ctx) {
CommandResult::Output(text) => {
assert!(text.contains("Identity: anonymous local agent"));
}
other => panic!("expected Output, got {other:?}"),
}
}
}