Skip to main content

command_stream/terminal/
types.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use std::fmt;
4use std::path::PathBuf;
5use std::time::Duration;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub struct TerminalResize {
9    pub cols: u16,
10    pub rows: u16,
11}
12
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum TerminalKey {
15    Backspace,
16    CtrlC,
17    CtrlD,
18    Down,
19    Enter,
20    Escape,
21    Left,
22    Right,
23    Tab,
24    Up,
25    Raw(String),
26}
27
28impl TerminalKey {
29    pub(crate) fn sequence(&self) -> &str {
30        match self {
31            Self::Backspace => "\u{7f}",
32            Self::CtrlC => "\u{3}",
33            Self::CtrlD => "\u{4}",
34            Self::Down => "\u{1b}[B",
35            Self::Enter => "\r",
36            Self::Escape => "\u{1b}",
37            Self::Left => "\u{1b}[D",
38            Self::Right => "\u{1b}[C",
39            Self::Tab => "\t",
40            Self::Up => "\u{1b}[A",
41            Self::Raw(sequence) => sequence,
42        }
43    }
44}
45
46#[derive(Debug, Clone, Default)]
47pub struct TerminalInteraction {
48    pub after: Option<String>,
49    pub after_regex: Option<String>,
50    pub idle_duration: Duration,
51    pub text: Option<String>,
52    pub key: Option<TerminalKey>,
53    pub resize: Option<TerminalResize>,
54}
55
56#[derive(Debug, Clone)]
57pub struct TerminalCaptureOptions {
58    pub file: String,
59    pub args: Vec<String>,
60    pub cwd: Option<PathBuf>,
61    pub env: HashMap<String, String>,
62    pub cols: u16,
63    pub rows: u16,
64    pub settle_duration: Duration,
65    /// Retain a frame after every parsed PTY write instead of waiting for idle.
66    pub frames_on_write: bool,
67    /// Disable recording history; keep the latest frame and last 1MB of output.
68    pub recording: bool,
69    pub interactions: Vec<TerminalInteraction>,
70    pub stop_marker: Option<String>,
71    pub stop_marker_grace: Duration,
72    /// Deadline for the whole run. `None` keeps the child alive indefinitely,
73    /// which is the default for [`crate::terminal::open_terminal`] sessions.
74    pub timeout: Option<Duration>,
75    pub artifact_directory: Option<PathBuf>,
76}
77
78impl Default for TerminalCaptureOptions {
79    fn default() -> Self {
80        Self {
81            file: String::new(),
82            args: Vec::new(),
83            cwd: None,
84            env: HashMap::new(),
85            cols: 80,
86            rows: 24,
87            settle_duration: Duration::from_millis(35),
88            frames_on_write: false,
89            recording: true,
90            interactions: Vec::new(),
91            stop_marker: None,
92            stop_marker_grace: Duration::from_millis(250),
93            timeout: Some(Duration::from_secs(30)),
94            artifact_directory: None,
95        }
96    }
97}
98
99#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
100pub struct TerminalCursor {
101    pub x: u16,
102    pub y: u16,
103}
104
105#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
106pub struct TerminalFrame {
107    pub time: f64,
108    pub cols: u16,
109    pub rows: u16,
110    pub cursor: TerminalCursor,
111    pub alternate: bool,
112    pub lines: Vec<String>,
113    pub screen: Vec<String>,
114}
115
116#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
117pub struct AsciicastHeader {
118    pub version: u8,
119    pub width: u16,
120    pub height: u16,
121    pub timestamp: i64,
122    pub env: HashMap<String, String>,
123}
124
125#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
126pub struct AsciicastEvent {
127    pub time: f64,
128    pub code: String,
129    pub data: String,
130}
131
132#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
133pub struct Asciicast {
134    pub header: AsciicastHeader,
135    pub events: Vec<AsciicastEvent>,
136}
137
138#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
139pub struct TerminalCapture {
140    pub exit_code: i32,
141    pub signal: Option<String>,
142    pub output: String,
143    pub transcript: String,
144    pub frames: Vec<TerminalFrame>,
145    pub interaction_count: usize,
146    pub asciicast: Asciicast,
147}
148
149#[derive(Debug)]
150pub struct TerminalCaptureError {
151    message: String,
152    partial: Option<Box<TerminalCapture>>,
153}
154
155impl TerminalCaptureError {
156    pub(crate) fn new(message: impl Into<String>, partial: Option<TerminalCapture>) -> Self {
157        Self {
158            message: message.into(),
159            partial: partial.map(Box::new),
160        }
161    }
162
163    pub fn partial_capture(&self) -> Option<&TerminalCapture> {
164        self.partial.as_deref()
165    }
166}
167
168impl fmt::Display for TerminalCaptureError {
169    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
170        formatter.write_str(&self.message)
171    }
172}
173
174impl std::error::Error for TerminalCaptureError {}