ht: Headless Terminal
github.com
github.com
The other potential goal I could imagine was automation. If that is the case, I would recommend the author to make that more clear in the examples and also to describe it in relation to `expect`, which would probably be my go to tool for such use cases.
Whatever the case it does look like a fun project, even if I don’t see any cases where it would be the right tool for the job.
This could be a nice case.
Example:
curl --silent --unix-socket /run/user/1000/docker.sock http://v1.41/version
From: https://dev.to/smac89/curl-to-docker-through-sockets-1mheYou could even do something like a reverse proxy to very limited paths although I tend to think that would ultimately be a bad idea and making your own http calls is probably better.
I see this as something like the console management for VPS. Back in the day, I remember reading about how prgmr.com had setup a console that you’d directly SSH into. That’s now this interface [1] (and a company name change), but I could see how programmatically working with this would be helpful.
I don't see why one would be any more comfortable exposing a shell to the internet than the Docker daemon. It grants _more_ capabilities. Either should likely be protected by authentication.
I'm not sure I'd want to expose the Docker port to the outside world (or outside of a strictly firewalled subnet). Even if it is wrapped, this seems to dangerous to me.
The service I talked about is not a shell. It's a command line program that operates as the shell when you login via SSH. Instead of bash/zsh/etc, this program runs instead. The purpose is to give the VM admin access for out-of-band management (serial console, reinstalling the OS, etc). I'm a big fan of this approach, where you don't necessarily get full access to the host, but you do have enough access to do the work that's needed (and still SSH encrypted). No more, no less. To me, this seems like a great approach for something like restricted VPS or container admin.
I ended up doing something similar for an SSH jump box a few jobs back where you could setup some basic admin things (like uploading SSH keys) using a CLI program that was used as an SSH shell.
To bring it back to the original post -- like others, I had a hard time seeing what the OP could be used for. Until I thought about this OOB CLI. It would be great for scripting access to something like this.
I was proposing that this persons web app not do any sort of subprocess automation via something like ht, and instead take in requests and talk to the docker daemon on clients behalf. Since that allows any sort of authentication or filtering that needs to happen.
I wasn't really seriously proposing the straight reverse proxy setup. That's one of those layer violations like PostREST that is either genius or lunacy. I haven't figured out which one.
none of the Linux greeters meet all my needs, so i fall back to `login`. but i still need a graphical program for actually entering in my password -- particularly because some of my devices don't have a physical keyboard (i.e. my phone). so i take the output of a framebuffer-capable on-screen-keyboard [1] and pipe that into `login`. but try actually doing that. try `cat mypassword.txt | login MobiusHorizons`. it doesn't work: `login` does some things on its stdin which only work on vtty. so instead i run login on /dev/tty1, and pipe the password into /dev/tty1 for the auth.
yes, this solution is terrible. a lot of things would make it less terrible. i could fix one of the greeters to work the way i need it (tried that). i could patch `login` (where it probably won't ever be upstreamed). i could integrate the OSK into the same input system the ttys use... or i could reach for `ht`. everything except the last one is a day or more of work.
1: https://gitlab.com/postmarketOS/buffybox/-/tree/master/unl0k...
Expect is what I tried to integrate with first. It falls over quite quickly with any kind of app that does anything mildly complicated with the terminal.
How exactly did `expect` fall over?
From what I can tell, expect does not provide the functionality of a stateful terminal server/client under the hood for you so it isn't as easy to grab "text" screenshots of a Terminal User Interface, which is one of the main motivations behind ht (will update the readme to make this main use-case more clear)
I built my own probably not very good equivalent of your thing called icommandlib. I'm going to investigate ripping it out and replacing it with your tool.
While working on headlong I tried out and implemented a variety of ‘tools’ (i.e., functions) like editFile(), findFile(), sendText(), checkTime(), searchWeb(), etc., which the agents call using LLM function calling.
A bunch of these ended up being functions that interacted with an underlying terminal. This is similar to how swe-agent works actually.
But I figured instead of writing a bunch of functions that sit between the LLM and the terminal, maybe let the LLM use a terminal more like a human does, i.e., by “typing” input into it and looking at snapshots of the current state of it. Needed a way to get those stateful text snapshots though.
I first tried using tmux and also looked to see if any existing libs provide the same functionality. Couldn’t find anything so teamed up with Marcin to design and make ht.
playing with the agent using the terminal directly has evolved into a hypothesis that I’ve been exploring: the terminal may be the “one tool to rule them all” - i.e., if an agent learns to use a terminal well it can do most of what humans do on our computers. Or maybe terminal + browser are the “two tools to rule them all”?
Not sure how useful ht will be for other use cases, but maybe!
Most of us running Minecraft servers on Linux have it wrapped in screen or tmux because the CLI is the only way to issue certain commands including stopping it properly.
This could provide an alternative.
I've never seen a systemd service example for Minecraft which allowed for sending commands to the server CLI and seeing the result without involving screen/tmux/etc. The top result on Google just doesn't allow command input at all, running the service "headless", the one on the official MC wiki uses screen, and the only other options I've seen use RCON which is neither secure nor does it show the responses you'd get on the MC console.
If there's a way to run just the straight Minecraft JAR as a background service and still be able to interact with it in the occasional cases where I need to I'm very interested.
For me this looks like
-- /etc/systemd/system/minecraft.service --
[Unit]
Description="Minecraft server service"
[Service]
Environment=JAVA_HOME="/usr/lib/jvm/java-22-openjdk"
WorkingDirectory=/home/steam/minecraft/1.20.6/
ExecStart=/usr/lib/jvm/java-22-openjdk/bin/java -Xmx4096M -Xms1024M -jar /home/steam/minecraft/1.20.6/server.jar nogui
User=steam
Group=steam
Sockets=minecraft.socket
StandardInput=socket
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
-- /etc/systemd/system/minecraft.socket -- [Socket]
ListenFIFO=%t/minecraft.stdin
Service=minecraft.service
-------------What this will do is add a systemd dependency on minecraft.service to start minecraft.socket first(which creates the fifo `/run/minecraft.stdin`) then setup minecraft.service to listen to this socket for it's StandardInput(while leaving stdout and stderr pointing towards the journal).
The service can then be started and set to automatically start on boot(`systemctl daemon-reload && systemctl enable --now minecraft`). While running, data can be written to the socket file with `echo` and redirection(e.x. `echo "help" > /run/minecraft.stdin`), and the output will be visible in the journal(`journalctl -xef --unit minecraft.service`)
If you set stderr/out to go over the socket as well, then you can attach something like `screen` to it and use it like a typical TTY(or `telnet`).
This uses the file `/run/minecraft.stdin` as the socket, but the documentation for systemd.socket shows that this can also be a TCP port to listen for connections(and systemd.service shows using regular files, but then you have to manually set them up).
The minecraft server has a built-in RCON server running on a separate port than can be enabled(https://wiki.vg/RCON), and once enabled can be interacted with an RCON client(like https://github.com/Tiiffi/mcrcon).
So instead of redirecting stdin to a systemd process, you can also just leave stdin disconnected and use the built-in RCON server to do commands every so often.
[1] - https://github.com/tstack/lnav/blob/master/test/scripty.cc [2] - https://github.com/tstack/lnav/blob/master/test/tui-captures...
Also a few people comparing to `expect`. I haven't used `expect` before, but it looks very cool. Their docs/readme seem only slightly more fleshed out than ours :-D Looks like the main way to use expect is via:
spawn ...
expect ...
send ...
expect ...
etc.
so, the expect syntax seems targeted more towards testing where you simultaneously get the output from the underlying binary and then check if it's what you expect (thus the name I guess). I can't see if there is a way to just get the current terminal "view" (aka text screenshot) via an expect command?ht is more geared towards scripting (or otherwise programmatically accessing) the terminal as a UI (aka Terminal UI). So ht always runs a terminal for you and gives you access to the current terminal state. Need to try out expect myself, but from what I can tell, it doesn't seem to always transparently run a Terminal for you.
There might already be some other existing tool that overlaps with the ht functionality, but we couldn't find it when looked around a bunch before building ht.
Am I wrong about that? (I may very well be since I haven't used `expect` before)
I'll certainly tuck it into my toolbox. Thanks :)
e.g.
1. make initial connect over ssh (e.g. spawn ssh cli process) 2. expect "login: " response 3. send "admin" 4. expect "password: " response 5. send "password" 6. expect "$ " 7. send "whoami\n" 8. etc etc
I guess that might in theory be possible to script a TUI with but I suspect it'd get pretty convoluted over an extended period of time.
(BTW I mentioned this in a comment elsewhere in this thread but check out https://crates.io/crates/termwiz to avoid re-inventing the wheel for a bunch of terminal-related functionality.)
Expect is also extra cool because of `autoexpect'.
generate an Expect script from observing a (shell) session
https://manpages.ubuntu.com/manpages/focal/en/man1/autoexpec...Using JSON as RPC also seems like a good idea, especially when SSH-ing in, as the language server protocol has shown.
That being said, I don't see this project doing anything (yet?) about escape sequences (for colors, clickable links, mouse and clipboard integrations, setting title and cwd, etc) and handling shortcuts (e.g. translating ctrl-c to sigint). There are very few terminal emulators that get all of that right (I think Kitty, iTerm, WezTerm), so it would be great if this project could lead towards more of them being written.
(There is also the part where a VT100-style display is a bit more than just a character buffer—there’s rewrapping on resize, for example, which IIUC be on for some lines and off for others—but let’s assume we’re willing to postpone updating window contents until the resize is concluded so can afford to do that on the application/VT100-processor side. Even though that feels like 1995.)
At the moment there's no support for mouse and clipboard - ht uses asciinema's avt for terminal emulation, where this was never needed (although may be added to avt if ht will need it).
Regarding the colors: internally there's full support for standard indexed colors (palette) and RGB. The "getView" call currently returns a plain text version of the screen buffer, stripped of all color information (plaing text is what andyk's headlong project, which uses ht, needs), but we can easily make it return color attributes for each cell or segment of text (either by modifying "getView", or adding complementary "getRichView" or sth like that).
* https://lib.rs/gh/wez/wezterm/wezterm / https://crates.io/crates/termwiz
(Projects that build on this crate includes "ratatui" among others...)
In particular, I note at least these specific feature areas:
* "support functions for applications interested in either displaying data to a terminal or in building a terminal emulator": https://lib.rs/crates/termwiz
* "to help with parsing input received from a terminal": https://docs.rs/termwiz/latest/termwiz/input/index.html
* "Model a cell in the terminal display": https://docs.rs/termwiz/latest/termwiz/cell/index.html
* "parse escape sequences and attach semantic meaning": https://docs.rs/termwiz/latest/termwiz/escape/index.html
* "abstraction over a terminal device": https://docs.rs/termwiz/latest/termwiz/terminal/index.html
* https://docs.rs/termwiz/latest/termwiz/color/index.html
* "cross platform API for working with the psuedo terminal (pty) interfaces": https://lib.rs/crates/portable-pty
* "Low level escape sequence parser": https://lib.rs/crates/vtparse
So, seems like quite a lot there to help avoid re-inventing the wheel for terminal-related functionality (for andyk/ku1ik & others).
I'm back to using tmux again, but for a while I was using another program dtach, to start vim sessions that I could disconnect and reattach to. Inside neovim I'd have a bunch of terminals & buffers & what not, so it felt redundant having tmux also hosting an event higher level environment.
Dtach is super super lightweight. Tmux is keeping copies of each screen in memory, is doing some reprocessing. Dtach is basically a pipe wrapping input a program's input and output, data in data out.
I even wrote a little shell script to let me very quickly 'dta my-proje' which will go autocomplete my-project name and either open the existing dtach session in that project or go create dtach session with vim in it. https://github.com/jauntywunderkind/dtachment/blob/master/dt...
It would be interesting to see something like dtach used for automation or scripting, as it seems targeted for. The idea of being able to relay around the input and output feels like it should have some neat uses. There isn't really a protocol, afaik, and there's definitely no retained state for a getView like ht. But it should in many ways function similarly?
ht seems to be almost "there" for me, and would allow me to easily build a sequence of actions. However, it's kinda missing the color representation, which is also a problem for me: I need to read the color of a specific row/column to know that a specific menu item is selected correctly, and then proceed from this.
Let's see to what it can evolve later.
I don’t think `expect` is targeted at this use case (though I am only learning about `expect` right now so could be wrong)
Having sat with it a few minutes though, why would I choose this over the classic:
https://core.tcl-lang.org/expect/index
or any of its analogs such as: https://pkg.go.dev/github.com/google/goexpect https://pexpect.readthedocs.io/en/stable/ https://www.rubydoc.info/gems/ruby_expect/1.6.0/RubyExpect/E...
edit: ah. it preserves the UI. This has potential.
It depends on one's definition of "feasible", I guess.
Building against the "most recent glibc pushed by GitHub" doesn't make it feasible but building against an older glibc (which you can still do on a container which is otherwise using a recent glibc) is at least somewhat feasible.
The primary benefit from building against musl is that it "coincidentally" means that even the people who don't care at all about supporting older systems end up doing so "accidentally" due to their desire to use musl (for, presumably, some other reason).
glibc compatibility breakage is the bane of my existence.
The situation is made worse by GitHub pushing people to build against the default extremely recent glibc in "ubuntu-latest" for binary artifact builds rather than what should be done: building against the oldest glibc possible (you can still do that within a "ubuntu-latest" container).
glibc breaks "version compatibility" at times for the most IMO ridiculous reasons but also has support for running binaries linked against older glibc versions on newer glibc that just gets completely ignored.
I would rather not multiply the number of tools I use, even though ht might be more appropriate in a clean-room environment.
I'm really confused as to why the tmux protocol can't be used directly, and entirely different systems like ht have to be created.
This project looks pretty interesting, maybe i'm missing something.
To send control-c to the terminal, for example, you'd send the following JSON message to ht:
{ "type": "input", "payload": "\x03" }Programs running in a terminal don't get individual components of composite key presses such as ctrl+a, shift+b, so they don't see "a with ctrl modifier" or "b with shift modifier". The modifier keys are handled by terminal emulator before sending the key's ascii value to the program, modifying the regular ascii letters appropriately. So when "a" (ascii value 0x61) is pressed while holding shift, its ascii value is ... shifted (down) by a constant 0x20, making it ascii 0x41, which represents "A". Similar with ctrl key, which shifts down the ascii value by 0x60, turning "a" into 0x01. So to send "ctrl+d" you send input with a single byte of value 0x04 ("d" ascii 0x64 minus 0x60). ht uses PTY under the hood, and this is how you send keyboard input into to a program via a PTY. This is kinda low level though, and there's definitely a possibility of implementing a high level input method in ht, which would parse string such as "<ctrl+d>" and automatically turn it into 0x04 before sending it to the process.
In other words, the way input in ht works right now was the easiest, simplest way of implementing this to get it out the door.