@moejay/wrightty 0.0.0 → 0.1.0

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 (92) hide show
  1. package/dist/client.d.ts +14 -0
  2. package/dist/client.js +83 -0
  3. package/dist/index.d.ts +3 -0
  4. package/dist/index.js +8 -0
  5. package/dist/terminal.d.ts +48 -0
  6. package/dist/terminal.js +210 -0
  7. package/dist/types.d.ts +90 -0
  8. package/dist/types.js +3 -0
  9. package/package.json +35 -15
  10. package/.github/workflows/ci.yml +0 -90
  11. package/.github/workflows/release.yml +0 -177
  12. package/Cargo.lock +0 -2662
  13. package/Cargo.toml +0 -38
  14. package/PROTOCOL.md +0 -1351
  15. package/README.md +0 -386
  16. package/agents/ceo/AGENTS.md +0 -24
  17. package/agents/ceo/HEARTBEAT.md +0 -72
  18. package/agents/ceo/SOUL.md +0 -33
  19. package/agents/ceo/TOOLS.md +0 -3
  20. package/agents/founding-engineer/AGENTS.md +0 -44
  21. package/crates/wrightty/Cargo.toml +0 -43
  22. package/crates/wrightty/src/client_cmds.rs +0 -366
  23. package/crates/wrightty/src/discover.rs +0 -78
  24. package/crates/wrightty/src/main.rs +0 -100
  25. package/crates/wrightty/src/server.rs +0 -100
  26. package/crates/wrightty/src/term.rs +0 -338
  27. package/crates/wrightty-bridge-ghostty/Cargo.toml +0 -27
  28. package/crates/wrightty-bridge-ghostty/src/ghostty.rs +0 -422
  29. package/crates/wrightty-bridge-ghostty/src/lib.rs +0 -2
  30. package/crates/wrightty-bridge-ghostty/src/main.rs +0 -146
  31. package/crates/wrightty-bridge-ghostty/src/rpc.rs +0 -307
  32. package/crates/wrightty-bridge-kitty/Cargo.toml +0 -26
  33. package/crates/wrightty-bridge-kitty/src/kitty.rs +0 -269
  34. package/crates/wrightty-bridge-kitty/src/lib.rs +0 -2
  35. package/crates/wrightty-bridge-kitty/src/main.rs +0 -124
  36. package/crates/wrightty-bridge-kitty/src/rpc.rs +0 -304
  37. package/crates/wrightty-bridge-tmux/Cargo.toml +0 -26
  38. package/crates/wrightty-bridge-tmux/src/lib.rs +0 -2
  39. package/crates/wrightty-bridge-tmux/src/main.rs +0 -119
  40. package/crates/wrightty-bridge-tmux/src/rpc.rs +0 -291
  41. package/crates/wrightty-bridge-tmux/src/tmux.rs +0 -215
  42. package/crates/wrightty-bridge-wezterm/Cargo.toml +0 -26
  43. package/crates/wrightty-bridge-wezterm/src/lib.rs +0 -2
  44. package/crates/wrightty-bridge-wezterm/src/main.rs +0 -119
  45. package/crates/wrightty-bridge-wezterm/src/rpc.rs +0 -339
  46. package/crates/wrightty-bridge-wezterm/src/wezterm.rs +0 -190
  47. package/crates/wrightty-bridge-zellij/Cargo.toml +0 -27
  48. package/crates/wrightty-bridge-zellij/src/lib.rs +0 -2
  49. package/crates/wrightty-bridge-zellij/src/main.rs +0 -125
  50. package/crates/wrightty-bridge-zellij/src/rpc.rs +0 -328
  51. package/crates/wrightty-bridge-zellij/src/zellij.rs +0 -199
  52. package/crates/wrightty-client/Cargo.toml +0 -16
  53. package/crates/wrightty-client/src/client.rs +0 -254
  54. package/crates/wrightty-client/src/lib.rs +0 -2
  55. package/crates/wrightty-core/Cargo.toml +0 -21
  56. package/crates/wrightty-core/src/input.rs +0 -212
  57. package/crates/wrightty-core/src/lib.rs +0 -4
  58. package/crates/wrightty-core/src/screen.rs +0 -325
  59. package/crates/wrightty-core/src/session.rs +0 -249
  60. package/crates/wrightty-core/src/session_manager.rs +0 -77
  61. package/crates/wrightty-protocol/Cargo.toml +0 -13
  62. package/crates/wrightty-protocol/src/error.rs +0 -8
  63. package/crates/wrightty-protocol/src/events.rs +0 -138
  64. package/crates/wrightty-protocol/src/lib.rs +0 -4
  65. package/crates/wrightty-protocol/src/methods.rs +0 -321
  66. package/crates/wrightty-protocol/src/types.rs +0 -201
  67. package/crates/wrightty-server/Cargo.toml +0 -23
  68. package/crates/wrightty-server/src/lib.rs +0 -2
  69. package/crates/wrightty-server/src/main.rs +0 -65
  70. package/crates/wrightty-server/src/rpc.rs +0 -455
  71. package/crates/wrightty-server/src/state.rs +0 -39
  72. package/examples/basic_command.py +0 -53
  73. package/examples/interactive_tui.py +0 -86
  74. package/examples/record_session.py +0 -96
  75. package/install.sh +0 -81
  76. package/sdks/node/package-lock.json +0 -85
  77. package/sdks/node/package.json +0 -44
  78. package/sdks/node/src/client.ts +0 -94
  79. package/sdks/node/src/index.ts +0 -19
  80. package/sdks/node/src/terminal.ts +0 -258
  81. package/sdks/node/src/types.ts +0 -105
  82. package/sdks/node/tsconfig.json +0 -17
  83. package/sdks/python/README.md +0 -96
  84. package/sdks/python/pyproject.toml +0 -42
  85. package/sdks/python/wrightty/__init__.py +0 -6
  86. package/sdks/python/wrightty/cli.py +0 -210
  87. package/sdks/python/wrightty/client.py +0 -136
  88. package/sdks/python/wrightty/mcp_server.py +0 -434
  89. package/sdks/python/wrightty/terminal.py +0 -333
  90. package/skills/wrightty/SKILL.md +0 -261
  91. package/src/lib.rs +0 -1
  92. 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;