use crate::client::{
CommandResult, DiscoveredProjectSummary, HealthReport, ManagedSessionView, ProjectFleetView,
fleet_state_glyph,
};
pub(crate) use trusty_common::slack_format::{code_block, mrkdwn_escape};
#[cfg(test)]
mod tests;
const SHORT_ID_LEN: usize = 8;
const MAX_OUTPUT_LINES: usize = 50;
pub struct SlackFormatter;
impl SlackFormatter {
pub fn format(result: &CommandResult) -> String {
match result {
CommandResult::Sessions(sessions) => format_sessions(sessions),
CommandResult::SessionDetail { id, events, .. } => format_session_detail(id, events),
CommandResult::OverseerStatus {
enabled,
handler,
decisions,
} => format!(
"*Overseer Status*\nHandler: `{handler}`\nEnabled: {}\n\
Recent decisions: allow ({}), block ({}), flag ({})",
if *enabled { "✅" } else { "❌" },
decisions.allow,
decisions.block,
decisions.flag,
),
CommandResult::TmuxSessions(sessions) => format_tmux(sessions),
CommandResult::DiscoveredProjects(projects) => format_discovered_projects(projects),
CommandResult::Adopted { session } => {
format!("✅ Adopted tmux session `{session}` for oversight")
}
CommandResult::Discovered { count } => {
if *count == 0 {
"🔍 No new Claude Code tmux sessions found".to_string()
} else {
format!("🔍 Discovered and adopted {count} Claude Code tmux session(s)")
}
}
CommandResult::ProjectRegistered { path } => {
format!("✅ Registered project `{path}`")
}
CommandResult::ConfigAnalysis {
project,
recommendations,
} => {
if recommendations.is_empty() {
format!(
"*Claude config* for `{project}`\nNo recommendations — config looks healthy."
)
} else {
let lines = recommendations
.iter()
.map(|r| format!("• {}", r.message))
.collect::<Vec<_>>()
.join("\n");
format!("*Claude config* for `{project}`\n{lines}")
}
}
CommandResult::Snapshot { session, output } => {
let lines = tail_lines(output);
if lines.is_empty() {
format!("Session `{session}`: empty pane")
} else {
format!("*Snapshot: {session}*\n{}", code_block(&lines))
}
}
CommandResult::Killed { session_id } => {
format!("🗑️ Session {} killed", short_id(session_id))
}
CommandResult::CommandSent { session, output } => {
if output.trim().is_empty() {
format!("📨 Sent to `{session}` — no output captured")
} else {
format!("*📨 {session}*\n{}", code_block(output))
}
}
CommandResult::ChatReply { reply } => reply.clone(),
CommandResult::Approved { session_id } => {
format!("✅ Permission approved for session {session_id}")
}
CommandResult::Denied { session_id } => {
format!("❌ Permission denied for session {session_id}")
}
CommandResult::PairCode {
code,
expires_in_seconds,
} => format!(
"*Pairing code:* `{code}`\nExpires in {} minutes",
expires_in_seconds / 60,
),
CommandResult::PairSuccess { chat_info } => {
format!("✅ Successfully paired! This channel ({chat_info}) is now registered.")
}
CommandResult::PairState { paired } => {
if *paired {
"✅ Bot is paired with this daemon. Type `/help` to see commands.".to_string()
} else {
"👋 trusty-mpm Slack bot — not yet paired. Run `tm slack pair` on your server."
.to_string()
}
}
CommandResult::AlertSubscriptions(lines) => {
format!("*Alert subscription*\n{}", lines.join("\n"))
}
CommandResult::Doctor(report) => format_doctor_report(report),
CommandResult::SessionStarted {
session,
workdir,
deployed,
} => {
let mode = if *deployed {
"launched (framework deployed)"
} else {
"connected (no deployment)"
};
format!("✅ Session *{session}* {mode}\n`{workdir}`")
}
CommandResult::ManagedSpawned {
id,
name,
state,
runtime,
attach_cmd,
} => format!(
"✅ Spawned *{name}* (`{}`) [{state}] runtime={runtime}\nattach: `{attach_cmd}`",
short_id(id),
),
CommandResult::ManagedAdopted {
id,
name,
state,
cwd,
runtime,
attach_cmd,
} => format!(
"✅ Adopted *{name}* (`{}`) [{state}] runtime={runtime}\ncwd: `{cwd}`\nattach: `{attach_cmd}`",
short_id(id),
),
CommandResult::ManagedSessions(sessions) => format_managed_sessions(sessions),
CommandResult::ManagedSession(view) => format_managed_session(view),
CommandResult::ManagedSent { id, tmux_name } => {
format!("📨 Sent to managed `{tmux_name}` ({})", short_id(id))
}
CommandResult::ManagedAnswered {
id,
answer,
tmux_name,
} => format!(
"✅ Answered decision on `{tmux_name}` ({}) → `{answer}`",
short_id(id),
),
CommandResult::ManagedActivity {
id,
state,
summary,
pending_decision,
..
} => {
let decision = pending_decision
.as_deref()
.map(|d| format!("\n⚠️ pending: {d}"))
.unwrap_or_default();
format!("*Activity {} [{state}]*\n{summary}{decision}", short_id(id))
}
CommandResult::ManagedAttachCmd { id, attach_cmd } => {
format!("*Attach {}*\n`{attach_cmd}`", short_id(id))
}
CommandResult::ManagedLifecycle {
id,
name,
state,
action,
} => format!("✅ {name} ({}) {action} → {state}", short_id(id)),
CommandResult::Help(text) => text.clone(),
CommandResult::Health(report) => format_health(report),
CommandResult::Error(msg) => format!("❌ {msg}"),
CommandResult::ManagedFleet(fleet) => format_fleet_by_project_slack(fleet),
}
}
}
fn format_sessions(sessions: &[crate::client::SessionSummary]) -> String {
if sessions.is_empty() {
return "No active sessions.".to_string();
}
let mut text = String::from("*trusty-mpm sessions*\n");
for s in sessions {
let dot = if s.status.eq_ignore_ascii_case("active") {
"🟢"
} else {
"🔴"
};
text.push_str(&format!(
"\n{dot} `{}` — {}\n 📁 `{}`",
short_id(&s.id),
s.status,
s.workdir,
));
}
text
}
fn format_session_detail(id: &str, events: &[String]) -> String {
if events.is_empty() {
format!("Session {id}: no recent events")
} else {
let lines = events
.iter()
.map(|e| format!("• {e}"))
.collect::<Vec<_>>()
.join("\n");
format!("*Session {}*\n{lines}", short_id(id))
}
}
fn format_tmux(sessions: &[crate::client::TmuxSessionSummary]) -> String {
if sessions.is_empty() {
return "No tmux sessions found.".to_string();
}
let lines = sessions
.iter()
.map(|s| {
let tag = if s.managed {
"🟢 managed"
} else {
"⚪ external"
};
format!("• `{}` — {tag}", s.name)
})
.collect::<Vec<_>>()
.join("\n");
format!("*tmux sessions*\n{lines}")
}
pub fn format_discovered_projects(projects: &[DiscoveredProjectSummary]) -> String {
if projects.is_empty() {
return "No projects discovered in Claude Code config.".to_string();
}
let mut text = String::from("*Discovered projects*\n");
for p in projects {
let last = p
.last_session
.as_deref()
.map(|s| s.split('T').next().unwrap_or(s).to_string())
.unwrap_or_else(|| "never".to_string());
text.push_str(&format!(
"\n📁 `{}`\n {} session(s) · last used {last}",
p.path, p.session_count,
));
}
text
}
pub fn format_managed_sessions(sessions: &[ManagedSessionView]) -> String {
if sessions.is_empty() {
return "No managed sessions.".to_string();
}
let mut text = String::from("*managed sessions*\n");
for s in sessions {
let flag = if s.pending_decision.is_some() {
" ⚠️"
} else {
""
};
text.push_str(&format!(
"\n• `{}` {} [{}]{flag}",
short_id(&s.id),
s.name,
s.state,
));
}
text
}
pub fn format_managed_session(view: &ManagedSessionView) -> String {
let mut text = format!(
"*{}* (`{}`) [{}]",
view.name,
short_id(&view.id),
view.state
);
if let Some(ws) = &view.workspace_path {
text.push_str(&format!("\n📁 `{ws}`"));
}
if let Some(repo) = &view.repo_url {
let branch = view.branch.as_deref().unwrap_or("");
text.push_str(&format!("\n🔗 {repo} {branch}"));
}
if let Some(d) = &view.pending_decision {
let def = view.proposed_default.as_deref().unwrap_or("");
text.push_str(&format!("\n⚠️ pending: {d} (default: `{def}`)"));
}
text
}
fn format_doctor_report(report: &crate::core::doctor::DoctorReport) -> String {
let mut text = String::from("*trusty-mpm doctor*\n");
for check in &report.checks {
text.push_str(&format!(
"\n{} *{}* — {}",
status_icon(check.status),
check.name,
check.message,
));
}
text.push_str(&format!(
"\n\n{} *overall: {}*",
status_icon(report.overall),
status_word(report.overall),
));
text
}
fn format_health(report: &HealthReport) -> String {
if !report.reachable {
return format!("❌ *daemon unreachable*\n`{}`", report.url);
}
let catalog = if report.catalog_unknown {
"catalog: never synced"
} else if report.catalog_stale {
"catalog: ⚠️ updates available"
} else {
"catalog: up to date"
};
format!(
"✅ *daemon {}*\n`{}`\n{}\nfleet: {} session(s), {} awaiting a decision",
report.status, report.url, catalog, report.managed_total, report.managed_pending_decisions,
)
}
fn status_icon(status: crate::core::doctor::CheckStatus) -> &'static str {
use crate::core::doctor::CheckStatus;
match status {
CheckStatus::Ok => "✅",
CheckStatus::Warn => "⚠️",
CheckStatus::Fail => "❌",
}
}
fn status_word(status: crate::core::doctor::CheckStatus) -> &'static str {
use crate::core::doctor::CheckStatus;
match status {
CheckStatus::Ok => "healthy",
CheckStatus::Warn => "warnings",
CheckStatus::Fail => "failed",
}
}
fn tail_lines(output: &str) -> String {
let lines: Vec<&str> = output.lines().collect();
lines[lines.len().saturating_sub(MAX_OUTPUT_LINES)..].join("\n")
}
fn format_fleet_by_project_slack(fleet: &[ProjectFleetView]) -> String {
if fleet.is_empty() {
return "No registered projects.".to_string();
}
let mut text = String::from("*fleet by project*");
for pf in fleet {
text.push_str(&format!("\n\n*{}* — `{}`", pf.project_name, pf.repo_url,));
if pf.sessions.is_empty() {
text.push_str("\n —");
} else {
for s in &pf.sessions {
let dot = fleet_state_glyph(&s.state);
let flag = if s.pending_decision.is_some() {
" ⚠️"
} else {
""
};
text.push_str(&format!(
"\n {dot} `{}` {} [{}]{flag}",
short_id(&s.id),
s.name,
s.state,
));
}
}
}
text
}
pub(crate) fn short_id(id: &str) -> String {
let mut chars = id.chars();
let head: String = chars.by_ref().take(SHORT_ID_LEN).collect();
if chars.next().is_some() {
format!("{head}…")
} else {
head
}
}