trusty-mpm 0.9.0

trusty-mpm: unified multi-agent orchestration platform (core, daemon, CLI, TUI, Telegram)
//! UI-agnostic command model.
//!
//! Why: the Telegram bot, the TUI, and the CLI all express the same operator
//! intents — list sessions, check status, approve a request, pair the bot. A
//! single typed command keeps every UI's dispatch exhaustive and lets the one
//! [`super::executor::CommandExecutor`] translate intent into daemon HTTP calls.
//! What: [`TrustyCommand`] enumerates every remote-management action. UI crates
//! convert their own native command representation into a `TrustyCommand` and
//! hand it to the executor.
//! Test: `cargo test -p trusty-mpm-client` covers the executor; conversions are
//! tested in the UI crates.

use std::path::PathBuf;

/// A UI-agnostic operator command.
///
/// Why: a typed command is the shared contract between every UI and the
/// executor — UIs parse their input into one of these, the executor maps each
/// to daemon calls.
/// What: the full set of remote-management actions trusty-mpm supports,
/// including the bot-pairing handshake.
/// Test: see the executor tests in `executor.rs`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TrustyCommand {
    /// List all managed sessions.
    Sessions,
    /// Show detailed status for one session.
    Status {
        /// Session id or friendly name.
        session_id: String,
    },
    /// Approve a session's pending permission request.
    Approve {
        /// Session id or friendly name.
        session_id: String,
    },
    /// Deny a session's pending permission request.
    Deny {
        /// Session id or friendly name.
        session_id: String,
    },
    /// Show overseer status (enabled, handler, recent decisions).
    Overseer,
    /// List all tmux sessions (trusty-mpm and external).
    Tmux,
    /// Discover projects from the Claude Code configuration.
    Projects,
    /// Auto-discover tmux sessions running Claude Code and adopt them.
    Discover,
    /// Adopt an external tmux session for oversight.
    Adopt {
        /// tmux session name to adopt.
        session: String,
    },
    /// Analyze Claude Code config for a project.
    Config {
        /// Project directory path.
        project: String,
    },
    /// Capture a tmux pane snapshot.
    Snapshot {
        /// tmux session name.
        session: String,
    },
    /// Kill a trusty-mpm session.
    Kill {
        /// Session id or friendly name.
        session_id: String,
    },
    /// Show the current alert subscription.
    Alerts,
    /// Pair the bot with the daemon.
    ///
    /// Why: `/pair <code>` confirms a code generated by `tm pair`; `/pair` with
    /// no code reports the current pairing status instead.
    /// What: `Some(code)` confirms that code, `None` queries status.
    Pair {
        /// The pairing code to confirm, or `None` to query status.
        code: Option<String>,
    },
    /// Send a prompt to a Claude Code session's tmux pane.
    ///
    /// Why: `/send <session> <prompt>` drives a running session remotely —
    /// type a prompt, let it run, read back the captured pane output.
    /// What: the session id/name/prefix to resolve and the prompt to send.
    Send {
        /// Session id, friendly name, or name prefix to resolve.
        session: String,
        /// The prompt line to type into the session's tmux pane.
        prompt: String,
    },
    /// Launch a Claude Code session, running the full framework-deployment
    /// sequence first.
    ///
    /// Why: `tm launch` is the full entry point — it deploys instructions,
    /// agents, and skills into the project (`prepare_session`) before starting
    /// or attaching to the tmux-hosted session.
    /// What: the project directory to launch in and an optional explicit tmux
    /// session name (the daemon derives one from the folder when `None`).
    Launch {
        /// Project directory to launch the session in.
        project: PathBuf,
        /// Optional explicit tmux session name.
        session_name: Option<String>,
    },
    /// Connect to — or start — a Claude Code session *without* deploying the
    /// framework.
    ///
    /// Why: `tm connect` is the lightweight sibling of `Launch`. It skips the
    /// deployment sequence entirely and only starts (or attaches to) the
    /// tmux-hosted session — idempotent: create when absent, attach when not.
    /// What: the project directory and an optional explicit tmux session name.
    Connect {
        /// Project directory to connect the session to.
        project: PathBuf,
        /// Optional explicit tmux session name.
        session_name: Option<String>,
    },
    /// First-contact command: report pairing status and onboarding guidance.
    Start,
    /// Run the full trusty-mpm system diagnostic.
    ///
    /// Why: `/doctor` verifies the whole stack — instructions, agents, skills,
    /// memory, and search — in one command so an operator can confirm a
    /// healthy install without inspecting each piece by hand.
    /// What: triggers `GET /api/v1/doctor` and renders the resulting report.
    Doctor,
    /// Send a free-text message to the cross-session coordinator.
    ///
    /// Why: `tm coordinator "<message>"` is the scripting/Telegram entry point
    /// for the coordinator — a message prefixed with `@session:` routes a
    /// command at that session, a plain message is answered by the LLM with
    /// full session context.
    /// What: the message text to send to `POST /api/v1/coordinator/chat`.
    CoordinatorChat {
        /// The message to send to the coordinator.
        message: String,
    },
    /// Show the command list.
    Help,
}

/// The `/help` text listing every command.
///
/// Why: a single source of truth for the help body shared by every UI.
/// What: returns the multi-line help string.
/// Test: `help_text_lists_every_command`.
pub fn help_text() -> &'static str {
    "trusty-mpm bot commands:\n\
     /sessions — list managed sessions\n\
     /status <id> — detailed session status\n\
     /approve <id> — approve a pending permission request\n\
     /deny <id> — deny a pending permission request\n\
     /overseer — show overseer status\n\
     /tmux — list all tmux sessions\n\
     /projects — discover projects from Claude Code config\n\
     /discover — auto-discover tmux sessions running Claude Code\n\
     /adopt <session> — adopt an external tmux session\n\
     /config <path> — analyze Claude Code config for a project\n\
     /snapshot <session> — capture a tmux pane\n\
     /kill <id> — kill a session\n\
     /send <session> <prompt> — send a prompt to a Claude Code session\n\
     /connect <path> — connect to or start a session (no deployment)\n\
     /alerts — show alert subscriptions\n\
     /pair <code> — pair this bot with your daemon\n\
     /start — pairing status and onboarding\n\
     /doctor — run full system diagnostic\n\
     /help — this message"
}

#[cfg(test)]
mod tests {
    use super::*;

    #[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",
            "/help",
        ] {
            assert!(text.contains(cmd), "help text missing {cmd}");
        }
    }

    #[test]
    fn pair_command_distinguishes_code_and_status() {
        let with_code = TrustyCommand::Pair {
            code: Some("A4X9KZ".into()),
        };
        let status = TrustyCommand::Pair { code: None };
        assert_ne!(with_code, status);
    }
}