cnctd-service-ssh 0.1.8

SSH command execution service - library and MCP server
Documentation
//! Data types for interactive shell session management.

use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Default terminal width
pub fn default_cols() -> u16 {
    120
}

/// Default terminal height
pub fn default_rows() -> u16 {
    40
}

/// Arguments for creating an interactive shell session
#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub struct ShellSessionCreateArgs {
    /// Target ID previously registered via `ssh_register_target`
    pub target_id: String,

    /// Optional human-readable name for this session
    pub name: Option<String>,

    /// Initial shell command (default: user's login shell)
    pub shell: Option<String>,

    /// Terminal width in columns
    #[serde(default = "default_cols")]
    pub cols: u16,

    /// Terminal height in rows
    #[serde(default = "default_rows")]
    pub rows: u16,

    /// Client identifier for session association (enables reconnection filtering)
    pub client_id: Option<String>,

    /// Environment variables to set
    #[serde(default)]
    pub env: HashMap<String, String>,
}

/// Arguments for writing input to a session
#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub struct ShellSessionWriteArgs {
    /// Session ID
    pub session_id: String,

    /// Input to send (supports escape sequences like \x03 for Ctrl+C)
    pub input: String,

    /// Whether to append a newline after input (default: false)
    #[serde(default)]
    pub newline: bool,
}

/// Output format for reading session output
#[derive(Debug, Clone, Copy, Deserialize, Serialize, JsonSchema, Default, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum OutputFormat {
    /// Raw text output from the buffer (includes ANSI escape sequences)
    #[default]
    Raw,
    /// Current visible terminal screen state (parsed, clean text)
    Screen,
    /// Both raw output and screen state
    Both,
    /// Raw output with ANSI escape sequences stripped
    Stripped,
}

/// Arguments for reading session output
#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub struct ShellSessionReadArgs {
    /// Session ID
    pub session_id: String,

    /// Output format: raw, screen, both, or stripped
    #[serde(default)]
    pub format: OutputFormat,

    /// Whether to consume (clear) the buffer after reading (default: true)
    #[serde(default = "default_true")]
    pub consume: bool,

    /// Maximum wait time for new output in milliseconds (0 = no wait)
    #[serde(default)]
    pub wait_ms: u64,

    /// Minimum bytes to wait for (with wait_ms timeout)
    #[serde(default)]
    pub min_bytes: usize,

    /// Wait until this pattern appears in the screen output (e.g., "❯" or "$ ")
    /// Takes precedence over wait_ms if both are specified
    #[serde(default)]
    pub wait_for_pattern: Option<String>,

    /// Wait until screen output stabilizes (no changes for this many ms)
    /// Useful for waiting until a TUI finishes rendering
    #[serde(default)]
    pub wait_for_stable_ms: Option<u64>,
}

fn default_true() -> bool {
    true
}

/// Arguments for listing sessions
#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub struct ShellSessionListArgs {
    /// Filter by target ID
    pub target_id: Option<String>,

    /// Filter by client ID
    pub client_id: Option<String>,

    /// Include disconnected sessions (default: true)
    #[serde(default = "default_true")]
    pub include_disconnected: bool,
}

/// Arguments for reconnecting to a session
#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub struct ShellSessionReconnectArgs {
    /// Session ID to reconnect to
    pub session_id: String,
}

/// Arguments for resizing a session
#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub struct ShellSessionResizeArgs {
    /// Session ID
    pub session_id: String,

    /// New width in columns
    pub cols: u16,

    /// New height in rows
    pub rows: u16,
}

/// Arguments for closing a session
#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub struct ShellSessionCloseArgs {
    /// Session ID to close
    pub session_id: String,

    /// Forcefully kill even if processes are running (default: false)
    #[serde(default)]
    pub force: bool,
}

/// State of a shell session
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum SessionState {
    /// Session is active and responsive
    Active,
    /// SSH connection dropped but tmux session may still exist remotely
    Disconnected,
    /// Session has been explicitly closed
    Closed,
}

/// Terminal screen state for TUI applications
#[derive(Debug, Clone, Serialize, JsonSchema)]
pub struct ScreenState {
    /// Screen content as text lines
    pub lines: Vec<String>,

    /// Cursor position (row, col) - 0-indexed
    pub cursor: (u16, u16),

    /// Terminal dimensions (cols, rows)
    pub size: (u16, u16),

    /// Whether alternate screen is active (e.g., vim, htop)
    pub alternate_screen: bool,

    /// Window title if set by application
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,
}

/// Result of creating a session
#[derive(Debug, Clone, Serialize, JsonSchema)]
pub struct ShellSessionCreateResult {
    /// Unique session ID
    pub session_id: String,

    /// Session info
    pub info: ShellSessionInfo,

    /// Initial screen state
    pub screen: ScreenState,
}

/// Result of writing to a session
#[derive(Debug, Clone, Serialize, JsonSchema)]
pub struct ShellSessionWriteResult {
    /// Session ID
    pub session_id: String,

    /// Number of bytes sent
    pub bytes_sent: usize,
}

/// Result of reading from a session
#[derive(Debug, Clone, Serialize, JsonSchema)]
pub struct ShellSessionReadResult {
    /// Session ID
    pub session_id: String,

    /// Raw output (if requested, may be with or without ANSI codes based on format)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub raw: Option<String>,

    /// Screen state (if requested)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub screen: Option<ScreenState>,

    /// Bytes remaining in buffer (if not consumed)
    pub buffer_size: usize,

    /// Whether data was truncated due to buffer overflow
    pub truncated: bool,

    /// Current session state
    pub state: SessionState,

    /// Whether wait_for_pattern matched (None if pattern wasn't requested)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub pattern_matched: Option<bool>,

    /// Whether output stabilized within timeout (None if wait_for_stable_ms wasn't requested)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub stabilized: Option<bool>,
}

/// Session info for listing
#[derive(Debug, Clone, Serialize, JsonSchema)]
pub struct ShellSessionInfo {
    /// Unique session ID
    pub id: String,

    /// SSH target ID
    pub target_id: String,

    /// Human-readable name
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,

    /// Client identifier
    #[serde(skip_serializing_if = "Option::is_none")]
    pub client_id: Option<String>,

    /// Current state
    pub state: SessionState,

    /// Session creation timestamp (ISO 8601)
    pub created_at: String,

    /// Last activity timestamp (ISO 8601)
    pub last_activity: String,

    /// Terminal dimensions (cols, rows)
    pub size: (u16, u16),
}

/// Result of listing sessions
#[derive(Debug, Clone, Serialize, JsonSchema)]
pub struct ShellSessionListResult {
    /// List of sessions
    pub sessions: Vec<ShellSessionInfo>,
}

/// Result of reconnecting to a session
#[derive(Debug, Clone, Serialize, JsonSchema)]
pub struct ShellSessionReconnectResult {
    /// Session ID
    pub session_id: String,

    /// Updated session info
    pub info: ShellSessionInfo,

    /// Current screen state
    pub screen: ScreenState,
}

/// Result of resizing a session
#[derive(Debug, Clone, Serialize, JsonSchema)]
pub struct ShellSessionResizeResult {
    /// Session ID
    pub session_id: String,

    /// New dimensions (cols, rows)
    pub size: (u16, u16),
}

/// Result of closing a session
#[derive(Debug, Clone, Serialize, JsonSchema)]
pub struct ShellSessionCloseResult {
    /// Session ID
    pub session_id: String,

    /// Whether the session existed and was closed
    pub closed: bool,
}