htty_core
htty-core: Core headless terminal functionality with bundled ht binary.
This package provides the minimal interface for running ht processes.
1""" 2htty-core: Core headless terminal functionality with bundled ht binary. 3 4This package provides the minimal interface for running ht processes. 5""" 6 7from .core import Cols, Command, HtArgs, HtEvent, Rows, find_ht_binary, run 8 9__all__ = ["HtArgs", "HtEvent", "find_ht_binary", "run", "Command", "Rows", "Cols", "__version__"] 10# [[[cog 11# import os 12# cog.out(f'__version__ = "{os.environ["HTTY_VERSION"]}"') 13# ]]] 14__version__ = "0.2.25" 15# [[[end]]]
139class HtArgs: 140 """ 141 The caller provides one of these when they want an `ht` process. 142 """ 143 144 def __init__( 145 self, 146 command: Command, 147 subscribes: Optional[list[HtEvent]] = None, 148 rows: Rows = None, 149 cols: Cols = None, 150 ) -> None: 151 self.command = command 152 self.subscribes = subscribes or [] 153 self.rows = rows 154 self.cols = cols 155 156 def get_command(self, ht_binary: Optional[str] = None) -> list[str]: 157 """Build the command line arguments for running ht. 158 159 Args: 160 ht_binary: Optional path to ht binary. If not provided, find_ht_binary() will be called. 161 162 Returns: 163 List of command arguments that would be passed to subprocess.Popen 164 """ 165 if ht_binary is None: 166 ht_binary = find_ht_binary() 167 168 cmd_args = [ht_binary] 169 170 # Add subscription arguments 171 if self.subscribes: 172 subscribe_strings = [event.value for event in self.subscribes] 173 cmd_args.extend(["--subscribe", ",".join(subscribe_strings)]) 174 175 # Add size arguments if specified 176 if self.rows is not None and self.cols is not None: 177 cmd_args.extend(["--size", f"{self.cols}x{self.rows}"]) 178 179 # Add separator and the command to run 180 cmd_args.append("--") 181 if isinstance(self.command, str): 182 cmd_args.extend(self.command.split()) 183 else: 184 cmd_args.extend(self.command) 185 186 return cmd_args
The caller provides one of these when they want an ht
process.
156 def get_command(self, ht_binary: Optional[str] = None) -> list[str]: 157 """Build the command line arguments for running ht. 158 159 Args: 160 ht_binary: Optional path to ht binary. If not provided, find_ht_binary() will be called. 161 162 Returns: 163 List of command arguments that would be passed to subprocess.Popen 164 """ 165 if ht_binary is None: 166 ht_binary = find_ht_binary() 167 168 cmd_args = [ht_binary] 169 170 # Add subscription arguments 171 if self.subscribes: 172 subscribe_strings = [event.value for event in self.subscribes] 173 cmd_args.extend(["--subscribe", ",".join(subscribe_strings)]) 174 175 # Add size arguments if specified 176 if self.rows is not None and self.cols is not None: 177 cmd_args.extend(["--size", f"{self.cols}x{self.rows}"]) 178 179 # Add separator and the command to run 180 cmd_args.append("--") 181 if isinstance(self.command, str): 182 cmd_args.extend(self.command.split()) 183 else: 184 cmd_args.extend(self.command) 185 186 return cmd_args
Build the command line arguments for running ht.
Args: ht_binary: Optional path to ht binary. If not provided, find_ht_binary() will be called.
Returns: List of command arguments that would be passed to subprocess.Popen
45class HtEvent(StrEnum): 46 """ 47 Event types that can be subscribed to from the ht process. 48 49 The original set of events is documented [in the ht repo](https://github.com/andyk/ht?tab=readme-ov-file#events). 50 51 Events added by `htty`: 52 53 - pid 54 - exitCode 55 - debug 56 - completed 57 """ 58 59 INIT = "init" 60 """ 61 Same as snapshot event (see below) but sent only once, as the first event after ht's start (when sent to 62 STDOUT) and upon establishing of WebSocket connection. 63 """ 64 65 SNAPSHOT = "snapshot" 66 """ 67 Terminal window snapshot. Sent when the terminal snapshot is taken with the takeSnapshot command. 68 69 Event data is an object with the following fields: 70 71 - cols - current terminal width, number of columns 72 - rows - current terminal height, number of rows 73 - text - plain text snapshot as multi-line string, where each line represents a terminal row 74 - seq - a raw sequence of characters, which when printed to a blank terminal puts it in the same state as 75 ht's virtual terminal 76 """ 77 78 OUTPUT = "output" 79 """ 80 Terminal output. Sent when an application (e.g. shell) running under ht prints something to the terminal. 81 82 Event data is an object with the following fields: 83 84 - seq - a raw sequence of characters written to a terminal, potentially including control sequences 85 (colors, cursor positioning, etc.) 86 """ 87 88 RESIZE = "resize" 89 """ 90 Terminal resize. Send when the terminal is resized with the resize command. 91 92 Event data is an object with the following fields: 93 94 - cols - current terminal width, number of columns 95 - rows - current terminal height, number of rows 96 """ 97 98 PID = "pid" 99 """ 100 ht runs the indicated command in `sh`. 101 This event provides the pid of that `sh` process 102 """ 103 104 EXIT_CODE = "exitCode" 105 """ 106 htty modified ht to stay open even after the command has completed. 107 This event indicates the exit code of the underlying command. 108 """ 109 110 COMMAND_COMPLETED = "commandCompleted" 111 """ 112 htty modified ht to run your command like so: 113 114 Previously, ht did the simple thing and ran your command like this: 115 ``` 116 sh -c '{command}'' 117 ``` 118 119 Sometimes, the PTY would shut down before all output was processed by ht, causing snapshots taken 120 after exit to be incomplete. 121 To fix this htty modified ht to run your like so: 122 123 ``` 124 sh -c '{command} ; exit_code=$? ; /path/to/ht wait-exit /path/to/a/temp/fifo ; exit $exit_code' 125 ``` 126 127 (The fifo is used to notify `ht wait-exit` that it's safe to exit) 128 129 Following this change, the command might complete at one time, and the exit code would be made available later. 130 This event indicates when the command completed, exitCode appears when the shell exits. 131 """ 132 133 DEBUG = "debug" 134 """ 135 These events contain messages that might be helpful for debugging `ht`. 136 """
Event types that can be subscribed to from the ht process.
The original set of events is documented in the ht repo.
Events added by htty
:
- pid
- exitCode
- debug
- completed
Same as snapshot event (see below) but sent only once, as the first event after ht's start (when sent to STDOUT) and upon establishing of WebSocket connection.
Terminal window snapshot. Sent when the terminal snapshot is taken with the takeSnapshot command.
Event data is an object with the following fields:
- cols - current terminal width, number of columns
- rows - current terminal height, number of rows
- text - plain text snapshot as multi-line string, where each line represents a terminal row
- seq - a raw sequence of characters, which when printed to a blank terminal puts it in the same state as ht's virtual terminal
Terminal output. Sent when an application (e.g. shell) running under ht prints something to the terminal.
Event data is an object with the following fields:
- seq - a raw sequence of characters written to a terminal, potentially including control sequences (colors, cursor positioning, etc.)
Terminal resize. Send when the terminal is resized with the resize command.
Event data is an object with the following fields:
- cols - current terminal width, number of columns
- rows - current terminal height, number of rows
ht runs the indicated command in sh
.
This event provides the pid of that sh
process
htty modified ht to stay open even after the command has completed. This event indicates the exit code of the underlying command.
htty modified ht to run your command like so:
Previously, ht did the simple thing and ran your command like this:
sh -c '{command}''
Sometimes, the PTY would shut down before all output was processed by ht, causing snapshots taken after exit to be incomplete. To fix this htty modified ht to run your like so:
sh -c '{command} ; exit_code=$? ; /path/to/ht wait-exit /path/to/a/temp/fifo ; exit $exit_code'
(The fifo is used to notify ht wait-exit
that it's safe to exit)
Following this change, the command might complete at one time, and the exit code would be made available later. This event indicates when the command completed, exitCode appears when the shell exits.
These events contain messages that might be helpful for debugging ht
.
189def find_ht_binary() -> str: 190 """Find the bundled ht binary.""" 191 # Check HTTY_HT_BIN environment variable first 192 env_path = os.environ.get("HTTY_HT_BIN") 193 if env_path and os.path.isfile(env_path): 194 return env_path 195 196 ht_exe = "ht" + (sysconfig.get_config_var("EXE") or "") 197 198 # First, try to find the binary relative to this package installation 199 pkg_file = __file__ # This file: .../site-packages/htty_core/core.py 200 pkg_dir = os.path.dirname(pkg_file) # .../site-packages/htty_core/ 201 site_packages = os.path.dirname(pkg_dir) # .../site-packages/ 202 python_env = os.path.dirname(site_packages) # .../lib/python3.x/ 203 env_root = os.path.dirname(python_env) # .../lib/ 204 actual_env_root = os.path.dirname(env_root) # The actual environment root 205 206 # Look for binary in the environment's bin directory 207 env_bin_path = os.path.join(actual_env_root, "bin", ht_exe) 208 if os.path.isfile(env_bin_path): 209 return env_bin_path 210 211 # Only look for the bundled binary - no system fallbacks 212 raise FileNotFoundError( 213 f"Bundled ht binary not found at expected location: {env_bin_path}. " 214 f"This indicates a packaging issue with htty-core." 215 )
Find the bundled ht binary.
218def run(args: HtArgs) -> subprocess.Popen[str]: 219 """ 220 Given some `HtArgs` object, run its command via ht. 221 222 `ht` connect 223 224 225 Returns a subprocess.Popen object representing the running ht process. 226 The caller is responsible for managing the process lifecycle. 227 """ 228 cmd_args = args.get_command() 229 230 # Start the process 231 return subprocess.Popen( 232 cmd_args, 233 stdin=subprocess.PIPE, 234 stdout=subprocess.PIPE, 235 stderr=subprocess.PIPE, 236 text=True, 237 bufsize=1, 238 )
Given some HtArgs
object, run its command via ht.
ht
connect
Returns a subprocess.Popen object representing the running ht process. The caller is responsible for managing the process lifecycle.