1use std::collections::HashMap;
2use std::path::Path;
3
4use chrono::{DateTime, Utc};
5use serde::{Deserialize, Serialize};
6use thiserror::Error;
7use vex_domain::{ShellId, WorkstreamId};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct Shell {
12 pub id: ShellId,
13 pub workstream_id: WorkstreamId,
14 pub created_at: DateTime<Utc>,
15 pub is_alive: bool,
16}
17
18#[derive(Debug, Clone)]
20pub struct ShellOutput {
21 pub data: Vec<u8>,
22 pub bell_detected: bool,
23}
24
25pub const DEFAULT_COLS: u16 = 80;
27pub const DEFAULT_ROWS: u16 = 24;
28pub const DEFAULT_SCROLLBACK_LINES: usize = 10_000;
29
30#[derive(Debug, Error)]
31pub enum ShellError {
32 #[error("shell not found: {0}")]
33 NotFound(String),
34 #[error("shell already dead: {0}")]
35 AlreadyDead(String),
36 #[error("spawn failed: {0}")]
37 SpawnFailed(String),
38 #[error("PTY error: {0}")]
39 PtyError(String),
40 #[error("IO error: {0}")]
41 Io(#[from] std::io::Error),
42}
43
44pub trait ShellPort: Send + Sync {
46 fn spawn(
47 &self,
48 workstream_id: &WorkstreamId,
49 working_dir: &Path,
50 command: Option<&str>,
51 id_prefix: Option<&str>,
52 env: Option<&HashMap<String, String>>,
53 ) -> Result<ShellId, ShellError>;
54
55 fn write_stdin(&self, id: &ShellId, data: &[u8]) -> Result<(), ShellError>;
56
57 fn read_output(&self, id: &ShellId) -> Result<ShellOutput, ShellError>;
58
59 fn resize(&self, id: &ShellId, rows: u16, cols: u16) -> Result<(), ShellError>;
60
61 fn kill(&self, id: &ShellId) -> Result<(), ShellError>;
62
63 fn is_alive(&self, id: &ShellId) -> Result<bool, ShellError>;
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69
70 #[test]
71 fn shell_output_default() {
72 let output = ShellOutput {
73 data: vec![0x41, 0x42],
74 bell_detected: false,
75 };
76 assert_eq!(output.data, vec![0x41, 0x42]);
77 assert!(!output.bell_detected);
78 }
79
80 #[test]
81 fn shell_error_display() {
82 let err = ShellError::NotFound("shell-1".into());
83 assert_eq!(err.to_string(), "shell not found: shell-1");
84 }
85
86 #[test]
87 fn default_dimensions() {
88 assert_eq!(DEFAULT_COLS, 80);
89 assert_eq!(DEFAULT_ROWS, 24);
90 assert_eq!(DEFAULT_SCROLLBACK_LINES, 10_000);
91 }
92}