@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,328 +0,0 @@
1
- //! jsonrpsee RPC module that maps wrightty protocol methods to zellij CLI action commands.
2
- //!
3
- //! Note: Zellij CLI actions operate on the focused/current pane within the active session.
4
- //! The session_id parameter is accepted but currently used only for validation (must match
5
- //! the active ZELLIJ_SESSION_NAME). Multi-pane targeting is not available via the CLI
6
- //! action interface without a WASM plugin.
7
-
8
- use jsonrpsee::types::ErrorObjectOwned;
9
- use jsonrpsee::RpcModule;
10
-
11
- use wrightty_protocol::error;
12
- use wrightty_protocol::methods::*;
13
- use wrightty_protocol::types::*;
14
-
15
- use crate::zellij;
16
-
17
- fn proto_err(code: i32, msg: impl Into<String>) -> ErrorObjectOwned {
18
- ErrorObjectOwned::owned(code, msg.into(), None::<()>)
19
- }
20
-
21
- fn not_supported(method: &str) -> ErrorObjectOwned {
22
- proto_err(error::NOT_SUPPORTED, format!("{method} is not supported by the zellij bridge"))
23
- }
24
-
25
- /// Encode a wrightty KeyInput to the text/bytes it represents.
26
- ///
27
- /// For most keys we use write-chars with escape sequences. For byte-level control,
28
- /// we use zellij action write with raw bytes.
29
- fn encode_key_to_escape(key: &KeyInput) -> String {
30
- match key {
31
- KeyInput::Shorthand(s) => shorthand_to_escape(s),
32
- KeyInput::Structured(event) => key_event_to_escape(event),
33
- }
34
- }
35
-
36
- fn shorthand_to_escape(s: &str) -> String {
37
- if let Some((modifier, key)) = s.split_once('+') {
38
- match modifier {
39
- "Ctrl" => {
40
- if key.len() == 1 {
41
- let ch = key.chars().next().unwrap();
42
- let ctrl = (ch.to_ascii_uppercase() as u8).wrapping_sub(b'@');
43
- return (ctrl as char).to_string();
44
- }
45
- if let Some(seq) = named_key_escape(key) {
46
- return seq.to_string();
47
- }
48
- }
49
- "Alt" => {
50
- let mut out = "\x1b".to_string();
51
- if key.len() == 1 {
52
- out.push_str(key);
53
- } else if let Some(seq) = named_key_escape(key) {
54
- out.push_str(seq);
55
- }
56
- return out;
57
- }
58
- _ => {}
59
- }
60
- }
61
-
62
- if let Some(seq) = named_key_escape(s) {
63
- return seq.to_string();
64
- }
65
-
66
- s.to_string()
67
- }
68
-
69
- fn named_key_escape(name: &str) -> Option<&'static str> {
70
- match name {
71
- "Enter" | "Return" => Some("\r"),
72
- "Tab" => Some("\t"),
73
- "Backspace" => Some("\x7f"),
74
- "Delete" => Some("\x1b[3~"),
75
- "Escape" | "Esc" => Some("\x1b"),
76
- "ArrowUp" | "Up" => Some("\x1b[A"),
77
- "ArrowDown" | "Down" => Some("\x1b[B"),
78
- "ArrowRight" | "Right" => Some("\x1b[C"),
79
- "ArrowLeft" | "Left" => Some("\x1b[D"),
80
- "Home" => Some("\x1b[H"),
81
- "End" => Some("\x1b[F"),
82
- "PageUp" => Some("\x1b[5~"),
83
- "PageDown" => Some("\x1b[6~"),
84
- "Insert" => Some("\x1b[2~"),
85
- _ => None,
86
- }
87
- }
88
-
89
- fn key_event_to_escape(event: &KeyEvent) -> String {
90
- let has_ctrl = event.modifiers.iter().any(|m| matches!(m, Modifier::Ctrl));
91
- let has_alt = event.modifiers.iter().any(|m| matches!(m, Modifier::Alt));
92
-
93
- if has_alt {
94
- let inner = key_event_to_escape(&KeyEvent {
95
- key: event.key.clone(),
96
- modifiers: event.modifiers.iter().filter(|m| !matches!(m, Modifier::Alt)).cloned().collect(),
97
- char: event.char.clone(),
98
- n: event.n,
99
- });
100
- return format!("\x1b{inner}");
101
- }
102
-
103
- match &event.key {
104
- KeyType::Char => {
105
- let ch = event.char.as_deref().unwrap_or("");
106
- if has_ctrl && ch.len() == 1 {
107
- let c = ch.chars().next().unwrap();
108
- let ctrl = (c.to_ascii_uppercase() as u8).wrapping_sub(b'@');
109
- return (ctrl as char).to_string();
110
- }
111
- ch.to_string()
112
- }
113
- KeyType::Enter => "\r".to_string(),
114
- KeyType::Tab => "\t".to_string(),
115
- KeyType::Backspace => "\x7f".to_string(),
116
- KeyType::Delete => "\x1b[3~".to_string(),
117
- KeyType::Escape => "\x1b".to_string(),
118
- KeyType::ArrowUp => "\x1b[A".to_string(),
119
- KeyType::ArrowDown => "\x1b[B".to_string(),
120
- KeyType::ArrowRight => "\x1b[C".to_string(),
121
- KeyType::ArrowLeft => "\x1b[D".to_string(),
122
- KeyType::Home => "\x1b[H".to_string(),
123
- KeyType::End => "\x1b[F".to_string(),
124
- KeyType::PageUp => "\x1b[5~".to_string(),
125
- KeyType::PageDown => "\x1b[6~".to_string(),
126
- KeyType::Insert => "\x1b[2~".to_string(),
127
- KeyType::F => {
128
- let n = event.n.unwrap_or(1);
129
- match n {
130
- 1 => "\x1bOP".to_string(),
131
- 2 => "\x1bOQ".to_string(),
132
- 3 => "\x1bOR".to_string(),
133
- 4 => "\x1bOS".to_string(),
134
- 5 => "\x1b[15~".to_string(),
135
- 6 => "\x1b[17~".to_string(),
136
- 7 => "\x1b[18~".to_string(),
137
- 8 => "\x1b[19~".to_string(),
138
- 9 => "\x1b[20~".to_string(),
139
- 10 => "\x1b[21~".to_string(),
140
- 11 => "\x1b[23~".to_string(),
141
- 12 => "\x1b[24~".to_string(),
142
- _ => String::new(),
143
- }
144
- }
145
- }
146
- }
147
-
148
- pub fn build_rpc_module() -> anyhow::Result<RpcModule<()>> {
149
- let mut module = RpcModule::new(());
150
-
151
- // --- Wrightty.getInfo ---
152
- module.register_async_method("Wrightty.getInfo", |_params, _state, _| async move {
153
- let session_name = zellij::session_name().unwrap_or_else(|_| "unknown".to_string());
154
- serde_json::to_value(GetInfoResult {
155
- info: ServerInfo {
156
- version: "0.1.0".to_string(),
157
- implementation: format!("wrightty-bridge-zellij@{session_name}"),
158
- capabilities: Capabilities {
159
- screenshot: vec![ScreenshotFormat::Text],
160
- max_sessions: 1,
161
- supports_resize: false,
162
- supports_scrollback: true,
163
- supports_mouse: false,
164
- supports_session_create: true,
165
- supports_color_palette: false,
166
- supports_raw_output: false,
167
- supports_shell_integration: false,
168
- events: vec![],
169
- },
170
- },
171
- })
172
- .map_err(|e| proto_err(-32603, e.to_string()))
173
- })?;
174
-
175
- // --- Session.create ---
176
- module.register_async_method("Session.create", |_params, _state, _| async move {
177
- zellij::new_pane()
178
- .await
179
- .map_err(|e| proto_err(error::SPAWN_FAILED, e.to_string()))?;
180
-
181
- let session_name = zellij::session_name()
182
- .map_err(|e| proto_err(error::SPAWN_FAILED, e.to_string()))?;
183
-
184
- serde_json::to_value(SessionCreateResult { session_id: session_name })
185
- .map_err(|e| proto_err(-32603, e.to_string()))
186
- })?;
187
-
188
- // --- Session.destroy ---
189
- module.register_async_method("Session.destroy", |_params, _state, _| async move {
190
- zellij::close_pane()
191
- .await
192
- .map_err(|e| proto_err(error::SESSION_NOT_FOUND, e.to_string()))?;
193
-
194
- serde_json::to_value(SessionDestroyResult { exit_code: None })
195
- .map_err(|e| proto_err(-32603, e.to_string()))
196
- })?;
197
-
198
- // --- Session.list ---
199
- module.register_async_method("Session.list", |_params, _state, _| async move {
200
- let sessions = zellij::list_sessions()
201
- .await
202
- .map_err(|e| proto_err(-32603, e.to_string()))?;
203
-
204
- let session_infos: Vec<SessionInfo> = sessions
205
- .into_iter()
206
- .map(|s| SessionInfo {
207
- session_id: s.name.clone(),
208
- title: s.name,
209
- cwd: None,
210
- cols: 80,
211
- rows: 24,
212
- pid: None,
213
- running: true,
214
- alternate_screen: false,
215
- })
216
- .collect();
217
-
218
- serde_json::to_value(SessionListResult { sessions: session_infos })
219
- .map_err(|e| proto_err(-32603, e.to_string()))
220
- })?;
221
-
222
- // --- Session.getInfo ---
223
- module.register_async_method("Session.getInfo", |params, _state, _| async move {
224
- let p: SessionGetInfoParams = params.parse()?;
225
- let session_name = zellij::session_name()
226
- .map_err(|e| proto_err(error::SESSION_NOT_FOUND, e.to_string()))?;
227
-
228
- if p.session_id != session_name {
229
- return Err(proto_err(error::SESSION_NOT_FOUND,
230
- format!("session {} not found (current: {})", p.session_id, session_name)));
231
- }
232
-
233
- let info = SessionInfo {
234
- session_id: session_name.clone(),
235
- title: session_name,
236
- cwd: None,
237
- cols: 80,
238
- rows: 24,
239
- pid: None,
240
- running: true,
241
- alternate_screen: false,
242
- };
243
-
244
- serde_json::to_value(info).map_err(|e| proto_err(-32603, e.to_string()))
245
- })?;
246
-
247
- // --- Input.sendText ---
248
- module.register_async_method("Input.sendText", |params, _state, _| async move {
249
- let p: InputSendTextParams = params.parse()?;
250
-
251
- zellij::write_chars(&p.text)
252
- .await
253
- .map_err(|e| proto_err(error::SESSION_NOT_FOUND, e.to_string()))?;
254
-
255
- Ok::<_, ErrorObjectOwned>(serde_json::json!({}))
256
- })?;
257
-
258
- // --- Input.sendKeys ---
259
- module.register_async_method("Input.sendKeys", |params, _state, _| async move {
260
- let p: InputSendKeysParams = params.parse()?;
261
-
262
- for key in &p.keys {
263
- let escape_seq = encode_key_to_escape(key);
264
- // Use write-chars for printable text; for sequences with escape codes use write (bytes)
265
- if escape_seq.is_ascii() && !escape_seq.contains('\x1b') && !escape_seq.contains('\r')
266
- && !escape_seq.contains('\t') && !escape_seq.contains('\x7f')
267
- {
268
- zellij::write_chars(&escape_seq)
269
- .await
270
- .map_err(|e| proto_err(error::SESSION_NOT_FOUND, e.to_string()))?;
271
- } else {
272
- let bytes = zellij::key_to_bytes(&escape_seq);
273
- zellij::write_bytes(&bytes)
274
- .await
275
- .map_err(|e| proto_err(error::SESSION_NOT_FOUND, e.to_string()))?;
276
- }
277
- }
278
-
279
- Ok::<_, ErrorObjectOwned>(serde_json::json!({}))
280
- })?;
281
-
282
- // --- Screen.getText ---
283
- module.register_async_method("Screen.getText", |params, _state, _| async move {
284
- let p: ScreenGetTextParams = params.parse()?;
285
-
286
- let mut text = zellij::dump_screen()
287
- .await
288
- .map_err(|e| proto_err(error::SESSION_NOT_FOUND, e.to_string()))?;
289
-
290
- if p.trim_trailing_whitespace {
291
- text = text
292
- .lines()
293
- .map(|line| line.trim_end())
294
- .collect::<Vec<_>>()
295
- .join("\n");
296
- }
297
-
298
- serde_json::to_value(ScreenGetTextResult { text })
299
- .map_err(|e| proto_err(-32603, e.to_string()))
300
- })?;
301
-
302
- // --- Terminal.getSize (not supported) ---
303
- module.register_async_method("Terminal.getSize", |_params, _state, _| async move {
304
- Err::<serde_json::Value, _>(not_supported("Terminal.getSize"))
305
- })?;
306
-
307
- // --- Terminal.resize (not supported) ---
308
- module.register_async_method("Terminal.resize", |_params, _state, _| async move {
309
- Err::<serde_json::Value, _>(not_supported("Terminal.resize"))
310
- })?;
311
-
312
- // --- Screen.getContents (not supported) ---
313
- module.register_async_method("Screen.getContents", |_params, _state, _| async move {
314
- Err::<serde_json::Value, _>(not_supported("Screen.getContents"))
315
- })?;
316
-
317
- // --- Screen.screenshot (not supported) ---
318
- module.register_async_method("Screen.screenshot", |_params, _state, _| async move {
319
- Err::<serde_json::Value, _>(not_supported("Screen.screenshot"))
320
- })?;
321
-
322
- // --- Input.sendMouse (not supported) ---
323
- module.register_async_method("Input.sendMouse", |_params, _state, _| async move {
324
- Err::<serde_json::Value, _>(not_supported("Input.sendMouse"))
325
- })?;
326
-
327
- Ok(module)
328
- }
@@ -1,199 +0,0 @@
1
- //! Functions that shell out to `zellij` CLI action commands.
2
- //!
3
- //! Requires the bridge to run from within a Zellij session (ZELLIJ_SESSION_NAME must be set),
4
- //! or set ZELLIJ_SESSION_NAME explicitly before starting the bridge.
5
-
6
- use tokio::process::Command;
7
-
8
- #[derive(Debug, Clone)]
9
- pub struct ZellijSession {
10
- pub name: String,
11
- }
12
-
13
- #[derive(Debug, thiserror::Error)]
14
- pub enum ZellijError {
15
- #[error("zellij command failed: {0}")]
16
- CommandFailed(String),
17
- #[error("failed to parse zellij output: {0}")]
18
- ParseError(String),
19
- #[error("session not found: {0}")]
20
- SessionNotFound(String),
21
- #[error("ZELLIJ_SESSION_NAME is not set — bridge must run inside a zellij session")]
22
- NoSessionName,
23
- #[error("io error: {0}")]
24
- Io(#[from] std::io::Error),
25
- }
26
-
27
- /// Get the current zellij session name from the environment.
28
- pub fn session_name() -> Result<String, ZellijError> {
29
- std::env::var("ZELLIJ_SESSION_NAME").map_err(|_| ZellijError::NoSessionName)
30
- }
31
-
32
- fn zellij_cmd(args: &[&str]) -> Command {
33
- let cmd_str = std::env::var("ZELLIJ_CMD").unwrap_or_else(|_| "zellij".to_string());
34
- let parts: Vec<&str> = cmd_str.split_whitespace().collect();
35
- let (program, prefix_args) = parts.split_first().expect("ZELLIJ_CMD must not be empty");
36
-
37
- let mut cmd = Command::new(program);
38
- for arg in prefix_args {
39
- cmd.arg(arg);
40
- }
41
- for arg in args {
42
- cmd.arg(arg);
43
- }
44
- cmd
45
- }
46
-
47
- fn zellij_action_cmd(action_args: &[&str]) -> Command {
48
- let mut args = vec!["action"];
49
- args.extend_from_slice(action_args);
50
- zellij_cmd(&args)
51
- }
52
-
53
- /// Check if zellij is reachable and we are inside a session.
54
- pub async fn health_check() -> Result<(), ZellijError> {
55
- // Verify ZELLIJ_SESSION_NAME is set
56
- let _ = session_name()?;
57
-
58
- // Verify zellij is installed and accessible
59
- let output = zellij_cmd(&["list-sessions"]).output().await?;
60
-
61
- if !output.status.success() {
62
- let stderr = String::from_utf8_lossy(&output.stderr);
63
- return Err(ZellijError::CommandFailed(format!("zellij not reachable: {stderr}")));
64
- }
65
-
66
- Ok(())
67
- }
68
-
69
- /// List active zellij sessions.
70
- pub async fn list_sessions() -> Result<Vec<ZellijSession>, ZellijError> {
71
- let output = zellij_cmd(&["list-sessions", "--no-formatting"]).output().await?;
72
-
73
- if !output.status.success() {
74
- let stderr = String::from_utf8_lossy(&output.stderr);
75
- return Err(ZellijError::CommandFailed(stderr.into_owned()));
76
- }
77
-
78
- let stdout = String::from_utf8_lossy(&output.stdout);
79
- let sessions: Vec<ZellijSession> = stdout
80
- .lines()
81
- .filter_map(|line| {
82
- let name = line.split_whitespace().next()?.to_string();
83
- if name.is_empty() { None } else { Some(ZellijSession { name }) }
84
- })
85
- .collect();
86
-
87
- Ok(sessions)
88
- }
89
-
90
- /// Dump the visible screen to a temp file and return its contents.
91
- pub async fn dump_screen() -> Result<String, ZellijError> {
92
- let tmp = tempfile::NamedTempFile::new()?;
93
- let path = tmp.path().to_string_lossy().into_owned();
94
-
95
- let output = zellij_action_cmd(&["dump-screen", &path]).output().await?;
96
-
97
- if !output.status.success() {
98
- let stderr = String::from_utf8_lossy(&output.stderr);
99
- return Err(ZellijError::CommandFailed(stderr.into_owned()));
100
- }
101
-
102
- let content = tokio::fs::read_to_string(&path).await?;
103
- Ok(content)
104
- }
105
-
106
- /// Dump the full scrollback to a temp file and return its contents.
107
- pub async fn dump_scrollback() -> Result<String, ZellijError> {
108
- let tmp = tempfile::NamedTempFile::new()?;
109
- let path = tmp.path().to_string_lossy().into_owned();
110
-
111
- let output = zellij_action_cmd(&["dump-screen", "--full", &path]).output().await?;
112
-
113
- if !output.status.success() {
114
- let stderr = String::from_utf8_lossy(&output.stderr);
115
- return Err(ZellijError::CommandFailed(stderr.into_owned()));
116
- }
117
-
118
- let content = tokio::fs::read_to_string(&path).await?;
119
- Ok(content)
120
- }
121
-
122
- /// Send literal text to the focused pane via `zellij action write-chars`.
123
- pub async fn write_chars(text: &str) -> Result<(), ZellijError> {
124
- let output = zellij_action_cmd(&["write-chars", text]).output().await?;
125
-
126
- if !output.status.success() {
127
- let stderr = String::from_utf8_lossy(&output.stderr);
128
- return Err(ZellijError::CommandFailed(stderr.into_owned()));
129
- }
130
-
131
- Ok(())
132
- }
133
-
134
- /// Send raw bytes to the focused pane via `zellij action write`.
135
- ///
136
- /// `bytes` should be space-separated decimal byte values, e.g. "13" for Enter.
137
- pub async fn write_bytes(bytes: &str) -> Result<(), ZellijError> {
138
- let output = zellij_action_cmd(&["write", bytes]).output().await?;
139
-
140
- if !output.status.success() {
141
- let stderr = String::from_utf8_lossy(&output.stderr);
142
- return Err(ZellijError::CommandFailed(stderr.into_owned()));
143
- }
144
-
145
- Ok(())
146
- }
147
-
148
- /// Open a new pane.
149
- pub async fn new_pane() -> Result<(), ZellijError> {
150
- let output = zellij_action_cmd(&["new-pane"]).output().await?;
151
-
152
- if !output.status.success() {
153
- let stderr = String::from_utf8_lossy(&output.stderr);
154
- return Err(ZellijError::CommandFailed(stderr.into_owned()));
155
- }
156
-
157
- Ok(())
158
- }
159
-
160
- /// Close the focused pane.
161
- pub async fn close_pane() -> Result<(), ZellijError> {
162
- let output = zellij_action_cmd(&["close-pane"]).output().await?;
163
-
164
- if !output.status.success() {
165
- let stderr = String::from_utf8_lossy(&output.stderr);
166
- return Err(ZellijError::CommandFailed(stderr.into_owned()));
167
- }
168
-
169
- Ok(())
170
- }
171
-
172
- /// Get tab names for the current session.
173
- pub async fn query_tab_names() -> Result<Vec<String>, ZellijError> {
174
- let output = zellij_action_cmd(&["query-tab-names"]).output().await?;
175
-
176
- if !output.status.success() {
177
- let stderr = String::from_utf8_lossy(&output.stderr);
178
- return Err(ZellijError::CommandFailed(stderr.into_owned()));
179
- }
180
-
181
- let stdout = String::from_utf8_lossy(&output.stdout);
182
- let names: Vec<String> = stdout
183
- .lines()
184
- .map(|l| l.trim().to_string())
185
- .filter(|l| !l.is_empty())
186
- .collect();
187
-
188
- Ok(names)
189
- }
190
-
191
- /// Convert a wrightty key to raw bytes for `zellij action write`.
192
- ///
193
- /// Returns the space-separated byte values as a string.
194
- pub fn key_to_bytes(text: &str) -> String {
195
- text.bytes()
196
- .map(|b| b.to_string())
197
- .collect::<Vec<_>>()
198
- .join(" ")
199
- }
@@ -1,16 +0,0 @@
1
- [package]
2
- name = "wrightty-client"
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 = "Rust client SDK for the Wrightty terminal automation protocol"
10
-
11
- [dependencies]
12
- wrightty-protocol = { version = "0.1.0", path = "../wrightty-protocol" }
13
- jsonrpsee = { version = "0.24", features = ["ws-client"] }
14
- tokio = { version = "1", features = ["full"] }
15
- serde = { version = "1", features = ["derive"] }
16
- serde_json = "1"