use std::sync::OnceLock;
mod sm_tools;
pub use sm_tools::{render_sm_tools, sm_ops_verbs, sm_session_verbs};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SpecKind {
Command,
SmOnly,
}
#[derive(Debug, Clone, Copy)]
pub struct CommandSpec {
pub name: &'static str,
pub aliases: &'static [&'static str],
pub args: &'static str,
pub summary: &'static str,
pub kind: SpecKind,
}
impl CommandSpec {
pub fn prompt_signature(&self) -> String {
if self.args.is_empty() {
format!("{}()", self.name)
} else {
let inner = self.args.replace(['<', '>'], "");
format!("{}({inner})", self.name)
}
}
}
const COMMANDS: &[CommandSpec] = &[
CommandSpec {
name: "sessions",
aliases: &[],
args: "",
summary: "list managed sessions",
kind: SpecKind::Command,
},
CommandSpec {
name: "status",
aliases: &[],
args: "<id>",
summary: "detailed session status",
kind: SpecKind::Command,
},
CommandSpec {
name: "approve",
aliases: &[],
args: "<id>",
summary: "approve a pending permission request",
kind: SpecKind::Command,
},
CommandSpec {
name: "deny",
aliases: &[],
args: "<id>",
summary: "deny a pending permission request",
kind: SpecKind::Command,
},
CommandSpec {
name: "overseer",
aliases: &[],
args: "",
summary: "show overseer status",
kind: SpecKind::Command,
},
CommandSpec {
name: "tmux",
aliases: &[],
args: "",
summary: "list all tmux sessions",
kind: SpecKind::Command,
},
CommandSpec {
name: "projects",
aliases: &[],
args: "",
summary: "discover projects from Claude Code config",
kind: SpecKind::Command,
},
CommandSpec {
name: "discover",
aliases: &[],
args: "",
summary: "auto-discover tmux sessions running Claude Code",
kind: SpecKind::Command,
},
CommandSpec {
name: "adopt",
aliases: &[],
args: "<session>",
summary: "adopt an external tmux session",
kind: SpecKind::Command,
},
CommandSpec {
name: "config",
aliases: &[],
args: "<path>",
summary: "analyze Claude Code config for a project",
kind: SpecKind::Command,
},
CommandSpec {
name: "snapshot",
aliases: &[],
args: "<session>",
summary: "capture a tmux pane",
kind: SpecKind::Command,
},
CommandSpec {
name: "kill",
aliases: &[],
args: "<id>",
summary: "kill a session",
kind: SpecKind::Command,
},
CommandSpec {
name: "send",
aliases: &[],
args: "<session> <prompt>",
summary: "send a prompt to a Claude Code session",
kind: SpecKind::Command,
},
CommandSpec {
name: "connect",
aliases: &[],
args: "<path>",
summary: "connect to or start a session (no deployment)",
kind: SpecKind::Command,
},
CommandSpec {
name: "alerts",
aliases: &[],
args: "",
summary: "show alert subscriptions",
kind: SpecKind::Command,
},
CommandSpec {
name: "pair",
aliases: &[],
args: "<code>",
summary: "pair this bot with your daemon",
kind: SpecKind::Command,
},
CommandSpec {
name: "start",
aliases: &[],
args: "",
summary: "pairing status and onboarding",
kind: SpecKind::Command,
},
CommandSpec {
name: "doctor",
aliases: &[],
args: "",
summary: "run full system diagnostic",
kind: SpecKind::Command,
},
CommandSpec {
name: "managed-new",
aliases: &["spawn"],
args: "<repo> <ref> <task>",
summary: "spawn a managed session from a repo + ref + task",
kind: SpecKind::Command,
},
CommandSpec {
name: "managed-list",
aliases: &[],
args: "",
summary: "list managed session-manager sessions",
kind: SpecKind::Command,
},
CommandSpec {
name: "managed-get",
aliases: &[],
args: "<id>",
summary: "fetch one managed session's record",
kind: SpecKind::Command,
},
CommandSpec {
name: "managed-send",
aliases: &[],
args: "<id> <text>",
summary: "inject text into a managed session's pane",
kind: SpecKind::Command,
},
CommandSpec {
name: "managed-answer",
aliases: &[],
args: "<id> <answer>",
summary: "answer a managed session's pending decision",
kind: SpecKind::Command,
},
CommandSpec {
name: "managed-activity",
aliases: &[],
args: "<id>",
summary: "inspect a managed session's activity",
kind: SpecKind::Command,
},
CommandSpec {
name: "managed-attach",
aliases: &[],
args: "<id>",
summary: "show a managed session's tmux attach command",
kind: SpecKind::Command,
},
CommandSpec {
name: "managed-stop",
aliases: &[],
args: "<id>",
summary: "stop a managed session's runtime (keeps the workspace)",
kind: SpecKind::Command,
},
CommandSpec {
name: "managed-resume",
aliases: &[],
args: "<id>",
summary: "resume a stopped managed session",
kind: SpecKind::Command,
},
CommandSpec {
name: "managed-decommission",
aliases: &[],
args: "<id>",
summary: "decommission a managed session (terminal; removes workspace)",
kind: SpecKind::Command,
},
CommandSpec {
name: "managed-adopt",
aliases: &[],
args: "<tmux_name> <cwd>",
summary: "adopt an existing tmux session into the managed store",
kind: SpecKind::Command,
},
CommandSpec {
name: "health",
aliases: &["healthcheck", "ping"],
args: "",
summary: "report daemon health (reachability, catalog freshness, fleet)",
kind: SpecKind::Command,
},
CommandSpec {
name: "help",
aliases: &[],
args: "",
summary: "this message",
kind: SpecKind::Command,
},
];
pub fn catalog() -> &'static [CommandSpec] {
COMMANDS
}
pub fn help_text() -> &'static str {
static HELP: OnceLock<String> = OnceLock::new();
HELP.get_or_init(|| {
let mut out = String::from("trusty-mpm bot commands:\n");
let lines: Vec<String> = COMMANDS
.iter()
.filter(|c| c.kind == SpecKind::Command)
.map(|c| {
if c.args.is_empty() {
format!("/{} — {}", c.name, c.summary)
} else {
format!("/{} {} — {}", c.name, c.args, c.summary)
}
})
.collect();
out.push_str(&lines.join("\n"));
out
})
.as_str()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn catalog_covers_every_command() {
let names: Vec<&str> = catalog()
.iter()
.filter(|c| c.kind == SpecKind::Command)
.map(|c| c.name)
.collect();
for expected in [
"sessions",
"status",
"approve",
"deny",
"overseer",
"tmux",
"projects",
"discover",
"adopt",
"config",
"snapshot",
"kill",
"send",
"connect",
"alerts",
"pair",
"start",
"doctor",
"managed-new",
"managed-list",
"managed-get",
"managed-send",
"managed-answer",
"managed-activity",
"managed-attach",
"managed-stop",
"managed-resume",
"managed-decommission",
"managed-adopt",
"health",
"help",
] {
assert!(names.contains(&expected), "catalog missing `{expected}`");
}
}
#[test]
fn health_verb_has_expected_aliases_and_no_status_collision() {
let health = catalog()
.iter()
.find(|c| c.name == "health")
.expect("catalog has a health verb");
assert!(health.aliases.contains(&"healthcheck"));
assert!(health.aliases.contains(&"ping"));
assert!(
!health.aliases.contains(&"status"),
"`status` must not alias health — it is the session status verb"
);
assert_eq!(health.kind, SpecKind::Command);
}
#[test]
fn catalog_has_no_duplicate_command_names_or_aliases() {
use std::collections::HashSet;
let mut seen: HashSet<&str> = HashSet::new();
for spec in catalog().iter().filter(|c| c.kind == SpecKind::Command) {
assert!(
seen.insert(spec.name),
"duplicate verb name `{}`",
spec.name
);
for alias in spec.aliases {
assert!(seen.insert(alias), "duplicate alias `{alias}`");
}
}
}
#[test]
fn help_text_lists_every_command() {
let text = help_text();
for cmd in [
"/sessions",
"/status",
"/approve",
"/deny",
"/overseer",
"/tmux",
"/projects",
"/discover",
"/adopt",
"/config",
"/snapshot",
"/kill",
"/send",
"/connect",
"/alerts",
"/pair",
"/start",
"/doctor",
"/managed-new",
"/managed-list",
"/managed-get",
"/managed-send",
"/managed-answer",
"/managed-activity",
"/managed-attach",
"/managed-stop",
"/managed-resume",
"/managed-decommission",
"/managed-adopt",
"/health",
"/help",
] {
assert!(text.contains(cmd), "help text missing {cmd}");
}
}
}