tuiless is an experimental CLI for driving stateful terminal sessions from stateless commands.
The core idea is:
- every CLI invocation is short-lived;
- a per-workspace background runtime owns the actual shell tabs;
- commands such as
exec,press,type,resize,snapshot, andfetchtalk to that runtime over local IPC; snapshotreturns the current visible terminal viewport as plain text.
This is intentionally similar in spirit to browser automation CLIs, but the target is a terminal tab rather than a DOM page. In v0, the only supported resource is a named shell tab.
This project is in an early v0 prototype state.
Working core path:
- auto-start a background runtime for the current working directory;
- create or reuse a named shell tab;
- execute shell input with
exec; - capture the current viewport with
snapshot; - fetch the full terminal text history with
fetch; - resize a tab;
- list open tabs;
- install the bundled
tuilessCodex skill withskill --path.
Known rough edges:
- Windows is the only currently implemented IPC target.
attachnow restores ANSI colors and uses an event-priority refresh loop, but still needs broader real-TUI validation.- mouse commands inject terminal mouse escape sequences, but target applications must enable mouse reporting.
- integration tests are not yet complete.
cargo buildThe development binary is:
.\target\debug\tuiless.exeOpen a named tab with an explicit simulated terminal size:
.\target\debug\tuiless.exe open tab_1 --cols 100 --rows 30Run a shell command inside that tab:
.\target\debug\tuiless.exe exec tab_1 "echo hello"Snapshot the current visible viewport:
.\target\debug\tuiless.exe snapshot tab_1Fetch the full text content accumulated in the tab:
.\target\debug\tuiless.exe fetch tab_1List tabs in the current workspace runtime:
.\target\debug\tuiless.exe listResize the simulated terminal:
.\target\debug\tuiless.exe resize tab_1 --cols 120 --rows 40Clean up the runtime:
.\target\debug\tuiless.exe close --allInstall the bundled tuiless skill to a local skills directory:
.\target\debug\tuiless.exe skill --path "$env:USERPROFILE\.codex\skills"tuiless computes a session key from the canonical current working directory. The first CLI command for that workspace starts a background runtime. Later CLI commands connect to the same runtime and operate on the same tab state.
Runtime state is scoped by workspace session key, while registry metadata lives outside the repo by default:
- registry files live under
%LOCALAPPDATA%\tuiless\registryon Windows; - set
TUILESS_REGISTRY_DIRto override the registry location; - tab names are scoped to the current workspace;
tab_1in one workspace is independent fromtab_1in another workspace.
tuiless open <tab> [--cols <n>] [--rows <n>]Ensures a tab exists. If the tab is new, it is created with the requested terminal size, or the default size if no size is provided.
tuiless snapshot <tab> [--wait-stable <ms>]Returns the current visible viewport as plain text. This is not a full scrollback dump.
The default stable wait is currently 150ms.
tuiless fetch <tab> [--wait-stable <ms>]Returns the full plain-text contents currently retained for the tab, including scrollback history rather than only the visible viewport. For fullscreen/alternate-screen TUIs, fetch also keeps a bounded history of distinct viewport frames so state remains inspectable after the TUI exits.
This command does not apply any truncation of its own.
The default stable wait is currently 150ms.
tuiless exec <tab> <line>Types <line> into the tab and presses Enter.
This is a shell-friendly convenience command. It does not mean "spawn a managed subprocess and wait for completion".
tuiless type <tab> <text>Injects printable text into the tab without pressing Enter.
tuiless press <tab> <key> [--ctrl] [--alt] [--shift] [--meta]Sends a key event. Both chord syntax and explicit modifier flags are supported.
Examples:
tuiless press tab_1 Enter
tuiless press tab_1 Ctrl+A
tuiless press tab_1 A --ctrl
tuiless press tab_1 Esc
tuiless press tab_1 UpMouse commands use terminal cell coordinates. The origin is the top-left cell, with zero-based x and y.
tuiless click <tab> --x <col> --y <row> [--button left]
tuiless drag <tab> --from-x <c1> --from-y <r1> --to-x <c2> --to-y <r2> [--button left]
tuiless wheel <tab> --delta-y <n> [--x <col> --y <row>]
tuiless mouse-down <tab> --x <col> --y <row> [--button left]
tuiless mouse-up <tab> --x <col> --y <row> [--button left]
tuiless mouse-move <tab> --x <col> --y <row>These commands guarantee event injection only. Whether the target application reacts depends on terminal mouse reporting support.
For negative wheel values, both --delta-y -3 and --delta-y=-3 are accepted.
tuiless resize <tab> --cols <n> --rows <n>Updates the tab's simulated terminal size.
tuiless attach <tab> [--wait-stable <ms>]Starts a minimal interactive terminal view for a tab. Detach with Ctrl+].
On start, attach syncs the tab to the current terminal size, fetches an ANSI-formatted frame, enters an alternate-screen raw terminal view, enables mouse capture, and forwards keyboard, mouse, and resize events to the tab.
attach requires an interactive TTY for both stdin and stdout; in non-interactive environments it now fails fast without mutating tab state.
--wait-stable applies to the initial frame only (default 1ms), so live input stays responsive.
Runtime updates use ANSI snapshots and skip unchanged frames to reduce flicker, while keeping snapshot/fetch as plain-text outputs for automation.
tuiless listLists known tabs for the current workspace runtime.
tuiless close <tab>
tuiless close --allCloses one tab or shuts down the current workspace runtime.
tuiless skill --path <path-to-skills-directory>Copies the bundled skills/tuiless package into <path-to-skills-directory>\tuiless.
This command is local-only and does not start or use the tab runtime.
serve is an internal background entrypoint used by regular commands to bootstrap the runtime. It is intentionally hidden from top-level CLI help.
The public model is deliberately simple:
- a tab is a shell-backed PTY session;
- terminal size is part of tab state;
- snapshots are viewport-oriented;
- fetch returns the retained full text history for a tab;
- raw input events and convenience actions both exist;
- higher-level locator APIs such as
click text=Submitare intentionally out of scope for v0.
Internally, the current implementation uses:
clapfor CLI parsing;tokionamed pipes for Windows IPC with length-prefixed byte-stream framing;portable-ptyfor PTY management;vt100for terminal screen parsing;crosstermfor the minimal attach surface.
When validating runtime behavior, run dependent commands serially. Do not run open, exec, snapshot, and process cleanup in parallel, because they target the same background runtime and can create misleading races.
Useful smoke sequence:
Get-Process tuiless -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
Remove-Item .tuiless -Recurse -Force -ErrorAction SilentlyContinue
cargo build
.\target\debug\tuiless.exe open smoke --cols 100 --rows 30
.\target\debug\tuiless.exe exec smoke "echo smoke-ok"
Start-Sleep -Milliseconds 800
.\target\debug\tuiless.exe snapshot smokeRun tests:
cargo test