@moejay/wrightty 0.0.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/dist/cli.d.ts +3 -0
  2. package/dist/cli.js +144 -0
  3. package/dist/client.d.ts +14 -0
  4. package/dist/client.js +83 -0
  5. package/dist/index.d.ts +3 -0
  6. package/dist/index.js +8 -0
  7. package/dist/terminal.d.ts +48 -0
  8. package/dist/terminal.js +210 -0
  9. package/dist/types.d.ts +90 -0
  10. package/dist/types.js +3 -0
  11. package/package.json +38 -15
  12. package/.github/workflows/ci.yml +0 -90
  13. package/.github/workflows/release.yml +0 -177
  14. package/Cargo.lock +0 -2662
  15. package/Cargo.toml +0 -38
  16. package/PROTOCOL.md +0 -1351
  17. package/README.md +0 -386
  18. package/agents/ceo/AGENTS.md +0 -24
  19. package/agents/ceo/HEARTBEAT.md +0 -72
  20. package/agents/ceo/SOUL.md +0 -33
  21. package/agents/ceo/TOOLS.md +0 -3
  22. package/agents/founding-engineer/AGENTS.md +0 -44
  23. package/crates/wrightty/Cargo.toml +0 -43
  24. package/crates/wrightty/src/client_cmds.rs +0 -366
  25. package/crates/wrightty/src/discover.rs +0 -78
  26. package/crates/wrightty/src/main.rs +0 -100
  27. package/crates/wrightty/src/server.rs +0 -100
  28. package/crates/wrightty/src/term.rs +0 -338
  29. package/crates/wrightty-bridge-ghostty/Cargo.toml +0 -27
  30. package/crates/wrightty-bridge-ghostty/src/ghostty.rs +0 -422
  31. package/crates/wrightty-bridge-ghostty/src/lib.rs +0 -2
  32. package/crates/wrightty-bridge-ghostty/src/main.rs +0 -146
  33. package/crates/wrightty-bridge-ghostty/src/rpc.rs +0 -307
  34. package/crates/wrightty-bridge-kitty/Cargo.toml +0 -26
  35. package/crates/wrightty-bridge-kitty/src/kitty.rs +0 -269
  36. package/crates/wrightty-bridge-kitty/src/lib.rs +0 -2
  37. package/crates/wrightty-bridge-kitty/src/main.rs +0 -124
  38. package/crates/wrightty-bridge-kitty/src/rpc.rs +0 -304
  39. package/crates/wrightty-bridge-tmux/Cargo.toml +0 -26
  40. package/crates/wrightty-bridge-tmux/src/lib.rs +0 -2
  41. package/crates/wrightty-bridge-tmux/src/main.rs +0 -119
  42. package/crates/wrightty-bridge-tmux/src/rpc.rs +0 -291
  43. package/crates/wrightty-bridge-tmux/src/tmux.rs +0 -215
  44. package/crates/wrightty-bridge-wezterm/Cargo.toml +0 -26
  45. package/crates/wrightty-bridge-wezterm/src/lib.rs +0 -2
  46. package/crates/wrightty-bridge-wezterm/src/main.rs +0 -119
  47. package/crates/wrightty-bridge-wezterm/src/rpc.rs +0 -339
  48. package/crates/wrightty-bridge-wezterm/src/wezterm.rs +0 -190
  49. package/crates/wrightty-bridge-zellij/Cargo.toml +0 -27
  50. package/crates/wrightty-bridge-zellij/src/lib.rs +0 -2
  51. package/crates/wrightty-bridge-zellij/src/main.rs +0 -125
  52. package/crates/wrightty-bridge-zellij/src/rpc.rs +0 -328
  53. package/crates/wrightty-bridge-zellij/src/zellij.rs +0 -199
  54. package/crates/wrightty-client/Cargo.toml +0 -16
  55. package/crates/wrightty-client/src/client.rs +0 -254
  56. package/crates/wrightty-client/src/lib.rs +0 -2
  57. package/crates/wrightty-core/Cargo.toml +0 -21
  58. package/crates/wrightty-core/src/input.rs +0 -212
  59. package/crates/wrightty-core/src/lib.rs +0 -4
  60. package/crates/wrightty-core/src/screen.rs +0 -325
  61. package/crates/wrightty-core/src/session.rs +0 -249
  62. package/crates/wrightty-core/src/session_manager.rs +0 -77
  63. package/crates/wrightty-protocol/Cargo.toml +0 -13
  64. package/crates/wrightty-protocol/src/error.rs +0 -8
  65. package/crates/wrightty-protocol/src/events.rs +0 -138
  66. package/crates/wrightty-protocol/src/lib.rs +0 -4
  67. package/crates/wrightty-protocol/src/methods.rs +0 -321
  68. package/crates/wrightty-protocol/src/types.rs +0 -201
  69. package/crates/wrightty-server/Cargo.toml +0 -23
  70. package/crates/wrightty-server/src/lib.rs +0 -2
  71. package/crates/wrightty-server/src/main.rs +0 -65
  72. package/crates/wrightty-server/src/rpc.rs +0 -455
  73. package/crates/wrightty-server/src/state.rs +0 -39
  74. package/examples/basic_command.py +0 -53
  75. package/examples/interactive_tui.py +0 -86
  76. package/examples/record_session.py +0 -96
  77. package/install.sh +0 -81
  78. package/sdks/node/package-lock.json +0 -85
  79. package/sdks/node/package.json +0 -44
  80. package/sdks/node/src/client.ts +0 -94
  81. package/sdks/node/src/index.ts +0 -19
  82. package/sdks/node/src/terminal.ts +0 -258
  83. package/sdks/node/src/types.ts +0 -105
  84. package/sdks/node/tsconfig.json +0 -17
  85. package/sdks/python/README.md +0 -96
  86. package/sdks/python/pyproject.toml +0 -42
  87. package/sdks/python/wrightty/__init__.py +0 -6
  88. package/sdks/python/wrightty/cli.py +0 -210
  89. package/sdks/python/wrightty/client.py +0 -136
  90. package/sdks/python/wrightty/mcp_server.py +0 -434
  91. package/sdks/python/wrightty/terminal.py +0 -333
  92. package/skills/wrightty/SKILL.md +0 -261
  93. package/src/lib.rs +0 -1
  94. package/tests/integration_test.rs +0 -618
@@ -1,307 +0,0 @@
1
- //! jsonrpsee RPC module that maps wrightty protocol methods to Ghostty IPC.
2
-
3
- use jsonrpsee::types::ErrorObjectOwned;
4
- use jsonrpsee::RpcModule;
5
-
6
- use wrightty_protocol::error;
7
- use wrightty_protocol::methods::*;
8
- use wrightty_protocol::types::*;
9
-
10
- use crate::ghostty;
11
-
12
- fn proto_err(code: i32, msg: impl Into<String>) -> ErrorObjectOwned {
13
- ErrorObjectOwned::owned(code, msg.into(), None::<()>)
14
- }
15
-
16
- fn not_supported(method: &str) -> ErrorObjectOwned {
17
- proto_err(
18
- error::NOT_SUPPORTED,
19
- format!("{method} is not supported by the ghostty bridge"),
20
- )
21
- }
22
-
23
- /// Parse a session ID string into a Ghostty window ID (u64).
24
- fn parse_window_id(session_id: &str) -> Result<u64, ErrorObjectOwned> {
25
- session_id
26
- .parse::<u64>()
27
- .map_err(|_| proto_err(error::SESSION_NOT_FOUND, format!("invalid session id: {session_id}")))
28
- }
29
-
30
- /// Convert a wrightty `KeyInput` to an xdotool-compatible key name.
31
- ///
32
- /// xdotool key names reference:
33
- /// <https://gitlab.com/cunidev/gestures/-/wikis/xdotool-list-of-key-codes>
34
- fn encode_key_to_xdotool(key: &KeyInput) -> String {
35
- match key {
36
- KeyInput::Shorthand(s) => shorthand_to_xdotool(s),
37
- KeyInput::Structured(event) => key_event_to_xdotool(event),
38
- }
39
- }
40
-
41
- fn shorthand_to_xdotool(s: &str) -> String {
42
- // Handle modifier combos like "ctrl+c", "alt+shift+f"
43
- if s.contains('+') {
44
- // xdotool uses "ctrl+c" style, just lowercase it
45
- return s
46
- .split('+')
47
- .map(normalize_key_name)
48
- .collect::<Vec<_>>()
49
- .join("+");
50
- }
51
- normalize_key_name(s)
52
- }
53
-
54
- fn normalize_key_name(name: &str) -> String {
55
- match name {
56
- "Enter" | "Return" => "Return".to_string(),
57
- "Tab" => "Tab".to_string(),
58
- "Backspace" => "BackSpace".to_string(),
59
- "Delete" => "Delete".to_string(),
60
- "Escape" | "Esc" => "Escape".to_string(),
61
- "ArrowUp" | "Up" => "Up".to_string(),
62
- "ArrowDown" | "Down" => "Down".to_string(),
63
- "ArrowRight" | "Right" => "Right".to_string(),
64
- "ArrowLeft" | "Left" => "Left".to_string(),
65
- "Home" => "Home".to_string(),
66
- "End" => "End".to_string(),
67
- "PageUp" => "Page_Up".to_string(),
68
- "PageDown" => "Page_Down".to_string(),
69
- "Insert" => "Insert".to_string(),
70
- "ctrl" | "Ctrl" | "control" | "Control" => "ctrl".to_string(),
71
- "alt" | "Alt" => "alt".to_string(),
72
- "shift" | "Shift" => "shift".to_string(),
73
- "super" | "Super" | "meta" | "Meta" => "super".to_string(),
74
- _ => name.to_lowercase(),
75
- }
76
- }
77
-
78
- fn key_event_to_xdotool(event: &KeyEvent) -> String {
79
- let has_ctrl = event.modifiers.iter().any(|m| matches!(m, Modifier::Ctrl));
80
- let has_alt = event.modifiers.iter().any(|m| matches!(m, Modifier::Alt));
81
- let has_shift = event.modifiers.iter().any(|m| matches!(m, Modifier::Shift));
82
-
83
- let base = match &event.key {
84
- KeyType::Char => event
85
- .char
86
- .as_deref()
87
- .map(normalize_key_name)
88
- .unwrap_or_default(),
89
- KeyType::Enter => "Return".to_string(),
90
- KeyType::Tab => "Tab".to_string(),
91
- KeyType::Backspace => "BackSpace".to_string(),
92
- KeyType::Delete => "Delete".to_string(),
93
- KeyType::Escape => "Escape".to_string(),
94
- KeyType::ArrowUp => "Up".to_string(),
95
- KeyType::ArrowDown => "Down".to_string(),
96
- KeyType::ArrowRight => "Right".to_string(),
97
- KeyType::ArrowLeft => "Left".to_string(),
98
- KeyType::Home => "Home".to_string(),
99
- KeyType::End => "End".to_string(),
100
- KeyType::PageUp => "Page_Up".to_string(),
101
- KeyType::PageDown => "Page_Down".to_string(),
102
- KeyType::Insert => "Insert".to_string(),
103
- KeyType::F => format!("F{}", event.n.unwrap_or(1)),
104
- };
105
-
106
- let mut parts: Vec<&str> = vec![];
107
- if has_ctrl {
108
- parts.push("ctrl");
109
- }
110
- if has_alt {
111
- parts.push("alt");
112
- }
113
- if has_shift {
114
- parts.push("shift");
115
- }
116
-
117
- if parts.is_empty() {
118
- base
119
- } else {
120
- format!("{}+{base}", parts.join("+"))
121
- }
122
- }
123
-
124
- pub fn build_rpc_module() -> anyhow::Result<RpcModule<()>> {
125
- let mut module = RpcModule::new(());
126
-
127
- // --- Wrightty.getInfo ---
128
- module.register_async_method("Wrightty.getInfo", |_params, _state, _| async move {
129
- serde_json::to_value(GetInfoResult {
130
- info: ServerInfo {
131
- version: "0.1.0".to_string(),
132
- implementation: "wrightty-bridge-ghostty".to_string(),
133
- capabilities: Capabilities {
134
- screenshot: vec![],
135
- max_sessions: 128,
136
- supports_resize: false,
137
- supports_scrollback: false,
138
- supports_mouse: false,
139
- supports_session_create: true,
140
- supports_color_palette: false,
141
- supports_raw_output: false,
142
- supports_shell_integration: false,
143
- events: vec![],
144
- },
145
- },
146
- })
147
- .map_err(|e| proto_err(-32603, e.to_string()))
148
- })?;
149
-
150
- // --- Session.create ---
151
- module.register_async_method("Session.create", |_params, _state, _| async move {
152
- let window_id = ghostty::new_window()
153
- .await
154
- .map_err(|e| proto_err(error::SPAWN_FAILED, e.to_string()))?;
155
-
156
- serde_json::to_value(SessionCreateResult {
157
- session_id: window_id.to_string(),
158
- })
159
- .map_err(|e| proto_err(-32603, e.to_string()))
160
- })?;
161
-
162
- // --- Session.destroy ---
163
- module.register_async_method("Session.destroy", |params, _state, _| async move {
164
- let p: SessionDestroyParams = params.parse()?;
165
- let window_id = parse_window_id(&p.session_id)?;
166
-
167
- ghostty::close_window(window_id)
168
- .await
169
- .map_err(|e| proto_err(error::SESSION_NOT_FOUND, e.to_string()))?;
170
-
171
- serde_json::to_value(SessionDestroyResult { exit_code: None })
172
- .map_err(|e| proto_err(-32603, e.to_string()))
173
- })?;
174
-
175
- // --- Session.list ---
176
- module.register_async_method("Session.list", |_params, _state, _| async move {
177
- let windows = ghostty::list_windows()
178
- .await
179
- .map_err(|e| proto_err(-32603, e.to_string()))?;
180
-
181
- let sessions: Vec<SessionInfo> = windows
182
- .into_iter()
183
- .map(|w| SessionInfo {
184
- session_id: w.id.to_string(),
185
- title: w.title,
186
- cwd: w.cwd,
187
- cols: w.cols,
188
- rows: w.rows,
189
- pid: w.pid,
190
- running: true,
191
- alternate_screen: false,
192
- })
193
- .collect();
194
-
195
- serde_json::to_value(SessionListResult { sessions })
196
- .map_err(|e| proto_err(-32603, e.to_string()))
197
- })?;
198
-
199
- // --- Session.getInfo ---
200
- module.register_async_method("Session.getInfo", |params, _state, _| async move {
201
- let p: SessionGetInfoParams = params.parse()?;
202
- let window_id = parse_window_id(&p.session_id)?;
203
-
204
- let w = ghostty::find_window(window_id)
205
- .await
206
- .map_err(|e| proto_err(error::SESSION_NOT_FOUND, e.to_string()))?;
207
-
208
- let info = SessionInfo {
209
- session_id: w.id.to_string(),
210
- title: w.title,
211
- cwd: w.cwd,
212
- cols: w.cols,
213
- rows: w.rows,
214
- pid: w.pid,
215
- running: true,
216
- alternate_screen: false,
217
- };
218
-
219
- serde_json::to_value(info).map_err(|e| proto_err(-32603, e.to_string()))
220
- })?;
221
-
222
- // --- Input.sendText ---
223
- module.register_async_method("Input.sendText", |params, _state, _| async move {
224
- let p: InputSendTextParams = params.parse()?;
225
- let window_id = parse_window_id(&p.session_id)?;
226
-
227
- ghostty::send_text(window_id, &p.text)
228
- .await
229
- .map_err(|e| proto_err(error::SESSION_NOT_FOUND, e.to_string()))?;
230
-
231
- Ok::<_, ErrorObjectOwned>(serde_json::json!({}))
232
- })?;
233
-
234
- // --- Input.sendKeys ---
235
- module.register_async_method("Input.sendKeys", |params, _state, _| async move {
236
- let p: InputSendKeysParams = params.parse()?;
237
- let window_id = parse_window_id(&p.session_id)?;
238
-
239
- for key in &p.keys {
240
- // Single literal characters go via send_text for better compatibility
241
- let is_literal_char =
242
- matches!(key, KeyInput::Shorthand(s) if s.len() == 1 && !s.contains('+'));
243
-
244
- if is_literal_char {
245
- let text = match key {
246
- KeyInput::Shorthand(s) => s.clone(),
247
- _ => unreachable!(),
248
- };
249
- ghostty::send_text(window_id, &text)
250
- .await
251
- .map_err(|e| proto_err(error::SESSION_NOT_FOUND, e.to_string()))?;
252
- } else {
253
- let key_str = encode_key_to_xdotool(key);
254
- ghostty::send_key(window_id, &key_str)
255
- .await
256
- .map_err(|e| proto_err(error::SESSION_NOT_FOUND, e.to_string()))?;
257
- }
258
- }
259
-
260
- Ok::<_, ErrorObjectOwned>(serde_json::json!({}))
261
- })?;
262
-
263
- // --- Terminal.getSize ---
264
- module.register_async_method("Terminal.getSize", |params, _state, _| async move {
265
- let p: TerminalGetSizeParams = params.parse()?;
266
- let window_id = parse_window_id(&p.session_id)?;
267
-
268
- let w = ghostty::find_window(window_id)
269
- .await
270
- .map_err(|e| proto_err(error::SESSION_NOT_FOUND, e.to_string()))?;
271
-
272
- serde_json::to_value(TerminalGetSizeResult {
273
- cols: w.cols,
274
- rows: w.rows,
275
- })
276
- .map_err(|e| proto_err(-32603, e.to_string()))
277
- })?;
278
-
279
- // --- Unsupported methods ---
280
-
281
- module.register_async_method("Screen.getText", |_params, _state, _| async move {
282
- Err::<serde_json::Value, _>(not_supported(
283
- "Screen.getText — Ghostty does not expose a screen-dump IPC; \
284
- use wrightty-server with ghostty-native support instead",
285
- ))
286
- })?;
287
-
288
- module.register_async_method("Screen.getContents", |_params, _state, _| async move {
289
- Err::<serde_json::Value, _>(not_supported("Screen.getContents"))
290
- })?;
291
-
292
- module.register_async_method("Screen.screenshot", |_params, _state, _| async move {
293
- Err::<serde_json::Value, _>(not_supported("Screen.screenshot"))
294
- })?;
295
-
296
- module.register_async_method("Terminal.resize", |_params, _state, _| async move {
297
- Err::<serde_json::Value, _>(not_supported(
298
- "Terminal.resize — Ghostty window sizing is not exposed via IPC",
299
- ))
300
- })?;
301
-
302
- module.register_async_method("Input.sendMouse", |_params, _state, _| async move {
303
- Err::<serde_json::Value, _>(not_supported("Input.sendMouse"))
304
- })?;
305
-
306
- Ok(module)
307
- }
@@ -1,26 +0,0 @@
1
- [package]
2
- name = "wrightty-bridge-kitty"
3
- version.workspace = true
4
- edition.workspace = true
5
- license.workspace = true
6
- authors.workspace = true
7
- repository.workspace = true
8
- homepage.workspace = true
9
- description = "Bridge that translates wrightty protocol calls into kitty remote control commands"
10
-
11
- [[bin]]
12
- name = "wrightty-bridge-kitty"
13
- path = "src/main.rs"
14
-
15
- [dependencies]
16
- wrightty-protocol = { version = "0.1.0", path = "../wrightty-protocol" }
17
-
18
- jsonrpsee = { version = "0.24", features = ["server"] }
19
- tokio = { version = "1", features = ["full"] }
20
- clap = { version = "4", features = ["derive"] }
21
- tracing = "0.1"
22
- tracing-subscriber = { version = "0.3", features = ["env-filter"] }
23
- anyhow = "1"
24
- serde = { version = "1", features = ["derive"] }
25
- serde_json = "1"
26
- thiserror = "1"
@@ -1,269 +0,0 @@
1
- //! Functions that shell out to `kitty @` remote control commands.
2
- //!
3
- //! Requires kitty to be started with `allow_remote_control yes` in kitty.conf,
4
- //! or launched with `--listen-on unix:/path/to/socket`.
5
- //!
6
- //! Set `KITTY_LISTEN_ON` to the socket path if kitty is not using the default.
7
-
8
- use serde::Deserialize;
9
- use tokio::process::Command;
10
-
11
- /// A kitty window as returned by `kitty @ ls`.
12
- #[derive(Debug, Clone, Deserialize)]
13
- pub struct KittyWindow {
14
- pub id: u64,
15
- pub title: String,
16
- pub is_focused: bool,
17
- pub columns: u16,
18
- pub lines: u16,
19
- #[serde(default)]
20
- pub pid: Option<u32>,
21
- #[serde(default)]
22
- pub cwd: Option<String>,
23
- }
24
-
25
- /// Intermediate structures for parsing `kitty @ ls` JSON output.
26
- #[derive(Debug, Deserialize)]
27
- struct KittyOsWindow {
28
- tabs: Vec<KittyTab>,
29
- }
30
-
31
- #[derive(Debug, Deserialize)]
32
- struct KittyTab {
33
- windows: Vec<KittyWindowRaw>,
34
- }
35
-
36
- #[derive(Debug, Deserialize)]
37
- struct KittyWindowRaw {
38
- id: u64,
39
- title: String,
40
- is_focused: bool,
41
- columns: u16,
42
- lines: u16,
43
- #[serde(default)]
44
- pid: Option<u32>,
45
- foreground_processes: Vec<KittyProcess>,
46
- }
47
-
48
- #[derive(Debug, Deserialize)]
49
- struct KittyProcess {
50
- pid: u32,
51
- cwd: String,
52
- }
53
-
54
- #[derive(Debug, thiserror::Error)]
55
- pub enum KittyError {
56
- #[error("kitty command failed: {0}")]
57
- CommandFailed(String),
58
- #[error("failed to parse kitty output: {0}")]
59
- ParseError(String),
60
- #[error("window {0} not found")]
61
- WindowNotFound(u64),
62
- #[error("io error: {0}")]
63
- Io(#[from] std::io::Error),
64
- }
65
-
66
- fn kitty_cmd(args: &[&str]) -> Command {
67
- let cmd_str = std::env::var("KITTY_CMD").unwrap_or_else(|_| "kitty".to_string());
68
- let parts: Vec<&str> = cmd_str.split_whitespace().collect();
69
- let (program, prefix_args) = parts.split_first().expect("KITTY_CMD must not be empty");
70
-
71
- let mut cmd = Command::new(program);
72
- for arg in prefix_args {
73
- cmd.arg(arg);
74
- }
75
- // Always prepend "@" subcommand for remote control
76
- cmd.arg("@");
77
-
78
- // If KITTY_LISTEN_ON is set, pass it as the --to argument
79
- if let Ok(socket) = std::env::var("KITTY_LISTEN_ON") {
80
- cmd.arg("--to");
81
- cmd.arg(socket);
82
- }
83
-
84
- for arg in args {
85
- cmd.arg(arg);
86
- }
87
- cmd
88
- }
89
-
90
- /// Check if kitty is reachable via remote control.
91
- pub async fn health_check() -> Result<(), KittyError> {
92
- let output = kitty_cmd(&["ls"]).output().await?;
93
-
94
- if !output.status.success() {
95
- let stderr = String::from_utf8_lossy(&output.stderr);
96
- return Err(KittyError::CommandFailed(format!("kitty not reachable: {stderr}")));
97
- }
98
-
99
- let os_windows: Vec<KittyOsWindow> = serde_json::from_slice(&output.stdout)
100
- .map_err(|e| KittyError::ParseError(e.to_string()))?;
101
-
102
- let total_windows: usize = os_windows
103
- .iter()
104
- .flat_map(|ow| ow.tabs.iter())
105
- .map(|t| t.windows.len())
106
- .sum();
107
-
108
- if total_windows == 0 {
109
- return Err(KittyError::CommandFailed("kitty has no windows".to_string()));
110
- }
111
-
112
- Ok(())
113
- }
114
-
115
- /// List all windows across all OS windows and tabs.
116
- pub async fn list_windows() -> Result<Vec<KittyWindow>, KittyError> {
117
- let output = kitty_cmd(&["ls"]).output().await?;
118
-
119
- if !output.status.success() {
120
- let stderr = String::from_utf8_lossy(&output.stderr);
121
- return Err(KittyError::CommandFailed(stderr.into_owned()));
122
- }
123
-
124
- let os_windows: Vec<KittyOsWindow> = serde_json::from_slice(&output.stdout)
125
- .map_err(|e| KittyError::ParseError(e.to_string()))?;
126
-
127
- let windows: Vec<KittyWindow> = os_windows
128
- .into_iter()
129
- .flat_map(|ow| ow.tabs)
130
- .flat_map(|t| t.windows)
131
- .map(|w| {
132
- let cwd = w.foreground_processes.first().map(|p| p.cwd.clone());
133
- let pid = w.pid.or_else(|| w.foreground_processes.first().map(|p| p.pid));
134
- KittyWindow {
135
- id: w.id,
136
- title: w.title,
137
- is_focused: w.is_focused,
138
- columns: w.columns,
139
- lines: w.lines,
140
- pid,
141
- cwd,
142
- }
143
- })
144
- .collect();
145
-
146
- Ok(windows)
147
- }
148
-
149
- /// Get screen text for a window via `kitty @ get-text --match id:<id> --extent screen`.
150
- pub async fn get_text(window_id: u64) -> Result<String, KittyError> {
151
- let match_str = format!("id:{window_id}");
152
- let output = kitty_cmd(&["get-text", "--match", &match_str, "--extent", "screen"])
153
- .output()
154
- .await?;
155
-
156
- if !output.status.success() {
157
- let stderr = String::from_utf8_lossy(&output.stderr);
158
- return Err(KittyError::CommandFailed(stderr.into_owned()));
159
- }
160
-
161
- Ok(String::from_utf8_lossy(&output.stdout).into_owned())
162
- }
163
-
164
- /// Get scrollback text for a window.
165
- pub async fn get_scrollback(window_id: u64) -> Result<String, KittyError> {
166
- let match_str = format!("id:{window_id}");
167
- let output = kitty_cmd(&["get-text", "--match", &match_str, "--extent", "all"])
168
- .output()
169
- .await?;
170
-
171
- if !output.status.success() {
172
- let stderr = String::from_utf8_lossy(&output.stderr);
173
- return Err(KittyError::CommandFailed(stderr.into_owned()));
174
- }
175
-
176
- Ok(String::from_utf8_lossy(&output.stdout).into_owned())
177
- }
178
-
179
- /// Send literal text to a window via `kitty @ send-text`.
180
- pub async fn send_text(window_id: u64, text: &str) -> Result<(), KittyError> {
181
- let match_str = format!("id:{window_id}");
182
- let output = kitty_cmd(&["send-text", "--match", &match_str, "--", text])
183
- .output()
184
- .await?;
185
-
186
- if !output.status.success() {
187
- let stderr = String::from_utf8_lossy(&output.stderr);
188
- return Err(KittyError::CommandFailed(stderr.into_owned()));
189
- }
190
-
191
- Ok(())
192
- }
193
-
194
- /// Send a key sequence to a window via `kitty @ send-key`.
195
- pub async fn send_key(window_id: u64, key: &str) -> Result<(), KittyError> {
196
- let match_str = format!("id:{window_id}");
197
- let output = kitty_cmd(&["send-key", "--match", &match_str, "--", key])
198
- .output()
199
- .await?;
200
-
201
- if !output.status.success() {
202
- let stderr = String::from_utf8_lossy(&output.stderr);
203
- return Err(KittyError::CommandFailed(stderr.into_owned()));
204
- }
205
-
206
- Ok(())
207
- }
208
-
209
- /// Launch a new kitty window. Returns the new window ID.
210
- pub async fn launch_window() -> Result<u64, KittyError> {
211
- let output = kitty_cmd(&["launch", "--type=window"]).output().await?;
212
-
213
- if !output.status.success() {
214
- let stderr = String::from_utf8_lossy(&output.stderr);
215
- return Err(KittyError::CommandFailed(stderr.into_owned()));
216
- }
217
-
218
- let stdout = String::from_utf8_lossy(&output.stdout);
219
- let window_id: u64 = stdout
220
- .trim()
221
- .parse()
222
- .map_err(|e: std::num::ParseIntError| KittyError::ParseError(e.to_string()))?;
223
-
224
- Ok(window_id)
225
- }
226
-
227
- /// Close a kitty window.
228
- pub async fn close_window(window_id: u64) -> Result<(), KittyError> {
229
- let match_str = format!("id:{window_id}");
230
- let output = kitty_cmd(&["close-window", "--match", &match_str]).output().await?;
231
-
232
- if !output.status.success() {
233
- let stderr = String::from_utf8_lossy(&output.stderr);
234
- return Err(KittyError::CommandFailed(stderr.into_owned()));
235
- }
236
-
237
- Ok(())
238
- }
239
-
240
- /// Resize a kitty window.
241
- pub async fn resize_window(window_id: u64, cols: u16, rows: u16) -> Result<(), KittyError> {
242
- let match_str = format!("id:{window_id}");
243
- let cols_str = cols.to_string();
244
- let rows_str = rows.to_string();
245
- let output = kitty_cmd(&[
246
- "resize-window",
247
- "--match", &match_str,
248
- "--width", &cols_str,
249
- "--height", &rows_str,
250
- ])
251
- .output()
252
- .await?;
253
-
254
- if !output.status.success() {
255
- let stderr = String::from_utf8_lossy(&output.stderr);
256
- return Err(KittyError::CommandFailed(stderr.into_owned()));
257
- }
258
-
259
- Ok(())
260
- }
261
-
262
- /// Find a window by ID.
263
- pub async fn find_window(window_id: u64) -> Result<KittyWindow, KittyError> {
264
- let windows = list_windows().await?;
265
- windows
266
- .into_iter()
267
- .find(|w| w.id == window_id)
268
- .ok_or(KittyError::WindowNotFound(window_id))
269
- }
@@ -1,2 +0,0 @@
1
- pub mod kitty;
2
- pub mod rpc;