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