Use this command to install Headless-TTY with WinGet:
winget install --id=Revoconner.HeadlessTTY -e
Headless-TTY Description
Headless-TTY is a terminal emulator designed specifically for Windows OS. It creates pseudo-terminals (PTYs) ensuring applications report isatty() as true, enabling seamless interaction with CLI tools and TUI applications without the need for a visible console window.
Key Features:
Real PTY Creation: Utilizes Windows ConPTY to create genuine pseudo-terminals.
Hidden Console Functionality: Runs processes without displaying a console window, capturing output programmatically.
System Tray Integration: Manages long-running tasks with a system tray icon for easy access and control.
ANSI Code Support: Maintains color and formatting in TUI applications by passing ANSI escape codes correctly.
Headless Execution: Enables running any CLI or TUI application invisibly, ideal for automation and background processes.
Ideal for developers, system administrators, and anyone needing to run CLI tools without a console. Users benefit from seamlessly interacting with TTY-dependent applications, automating tasks inconspicuously, and integrating command-line tools more effectively into Windows environments.
Audience & Benefit:
Headless-TTY can be installed via winget, offering a professional solution for managing terminal emulators in a Windows environment.
README
Headless TTY
A headless terminal wrapper built for native Windows OS, that keeps isatty() returning true for spawned processes while staying either completely invisible or hiding in system tray.
Download the latest version or one that suites your need
What It Does
Creates a real pseudo-terminal (PTY) via Windows ConPTY
Spawned processes see isatty(stdin) = true, isatty(stdout) = true
No visible console window needed - output is captured programmatically
ANSI escape codes pass through correctly
v2.5.0 - Now supports tray icon using --sys-tray argument. If you need a console for a long running process to see logs, or outputs just show from system tray and hide it back.
v2.5.0 - Right-click tray icon to show/hide console on demand with full color output and VT sequences output support.
Why This Matters
Many CLI tools check isatty() to decide behavior:
Claude CLI: Requires TTY for interactive mode, else crashes
Git: Colors output only when TTY detected
Any NODE JS app using INK library for TUI
Pythonw: Interactive mode depends on TTY
Without a real PTY, hiding a console window breaks these tools because redirected STDIN/STDOUT report isatty() = false.
Does a non C++ developer has any use of it?
Kind of! One still has to be somewhat technically inclined.
When you want to start a CLI app headless at Windows starts using task scheduler, you usually create a bat file then a vbs script to spawn that bat file headless.
You can now simple use headless-tty to spawn cmd, or powershell etc, then call whatver you want from there. A few examples:
headless-tty.exe -- python main.py : run a python file main.py without console, I mean you could use pythonw, but you still have to call it from somewhere.
headless-tty.exe -- cmd /c "ipconfig -all >%temp%\ipconfig.txt && notepad %temp%\ipconfig.txt" : Prints ipconfig to a file in temporary folder, then opens it in notepad, without ever showing console.
headless-tty.exe -- powershell ".\myscript.ps1" : run a powershell script without console.
headless-tty.exe --sys-tray -- python -u main.py : Run a long running python script but now with tray icon if you want to see outputs later, hide it away when not in use.
These are basic example of a non cpp dev using this.
Building
Requirements
Windows 10 version 1809+ (ConPTY support)
clang
CMake 3.16+
Build Steps
# Using the build script
build.bat
Usage
# Run cmd.exe (default)
headless-tty.exe
# Run a specific command
headless-tty.exe claude
# Pass arguments to command
headless-tty.exe cmd /c dir
System Tray Mode
Run processes in the background with a system tray icon. Right-click the tray icon to show/hide a console window on demand.
# Run with system tray icon
headless-tty.exe --sys-tray -- python -u main.py
# Run a long-running process in tray
headless-tty.exe --sys-tray -- node server.js
How it works:
The process runs completely hidden
A tray icon appears in your system tray (bottom-right)
Right-click the icon to "Show Console" or "Hide Console"
The console lets you see output and type commands
Closing the console window (X button) exits everything
If the child process exits, the tray icon disappears automatically
Use with pythonw (example use case, not limited to) to launch claude code cli in headless mode but keep session alive
You can also use the ConPTY wrapper as a library in your own C++ projects:
#include
int main() {
headless_tty::HeadlessTTY tty;
headless_tty::Config config;
config.size = { 120, 40 };
config.command = L"claude.exe";
// Set callback for output
tty.set_output_callback([](const uint8_t* data, size_t len) {
// Process output bytes
fwrite(data, 1, len, stdout);
});
if (!tty.start(config)) {
std::cerr << "Failed: " << tty.get_last_error() << std::endl;
return 1;
}
// Wait for process to exit
int exitCode = tty.wait();
return exitCode;
}
Options
Option
Description
--sys-tray
Run with system tray icon (right-click for menu)
--help, -h
Show help message
API Reference
headless_tty::ConPTY
Low-level ConPTY wrapper.
Method
Description
initialize(size)
Create the pseudo console
spawn(cmd, args, cwd)
Spawn a process attached to the PTY
write(data, len)
Write input to the PTY
set_output_callback(cb)
Set callback for PTY output
start_reading()
Start background read thread
stop()
Terminate process and cleanup
is_running()
Check if process is still running
wait(timeout)
Wait for process to exit
resize(size)
Resize the PTY
headless_tty::HeadlessTTY
High-level wrapper that manages the full lifecycle.
Method
Description
start(config)
Initialize and spawn process
write(str)
Send input to process
set_output_callback(cb)
Set callback for output
stop()
Stop the process
is_running()
Check if running
wait(timeout)
Wait for exit
Note
Bidirectional Process Termination
The process lifecycle is managed bidirectionally:
Parent killed -> Child dies: A Job Object with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE ensures the child process (and all its descendants) are terminated when headless-tty exits, even if killed forcefully.
Child exits -> Parent exits: A monitor thread watches the child process handle. When the child exits (e.g., user closes notepad), the monitor calls ClosePseudoConsole() which terminates conhost and breaks the pipes, causing headless-tty to exit cleanly.
Limitation: Although it works great for win32 apps as well as UWP apps (we are only talking about GUI here, all CLI apps works perfectly), there's a caveat in the UWP app, that it spawns the multiple PID. If you forcefully kill any child PID in the middle of the chain, you may leave orphan processes.
This prevents orphaned processes in both directions.
To disable parent->child termination, remove from pty.cpp:
To disable child->parent termination, remove monitor_loop() and related code.
Helper file - Messenger.cpp
This tiny executable file must be spawned with UAC elevation.
Mostly needed if you want an INK - https://github.com/vadimdemedes/ink
app such as gemini cli or claude code...
you will need their pid and this to send message.
Such apps, while will get the message from emulator,
but won't process return key to send.
Implementation pseudocode:
pseudocode: messenger_wrapper.py
import os, time, hmac, hashlib, subprocess, secrets
from pathlib import Path
class MessengerAuth:
def __init__(self, target_pid, target_name):
self.secret = secrets.token_bytes(32) # Raw bytes
self.target_pid = target_pid
self.target_name = target_name
self.pipe_name = f"\\\\.\\pipe\\InjectorAuth_{os.getpid()}"
def start_pipe_server(self):
# Create named pipe, serve on connect:
# Send: f"{self.secret.hex()}\n{self.target_pid}\n{self.target_name}"
# Verify caller binary hash before responding (optional)
pass
def sign(self, command):
ts = str(int(time.time()))
msg = f"{self.target_pid}|{command}|{ts}"
sig = hmac.new(self.secret, msg.encode(), hashlib.sha256).hexdigest()
return ts, sig
def send(self, command):
ts, sig = self.sign(command)
result = subprocess.run([
"messenger.exe",
str(self.target_pid),
command,
ts,
sig
])
return result.returncode
# Usage
auth = MessengerAuth(pid=12345, target_name="claude.exe")
auth.start_pipe_server() # In background thread
auth.send("hello world") # Text + Enter
auth.send("--tab") # Special key
auth.send("--escape")
Changelog
1.0.0
Development - not released
1.5.0
Initial release
Uses c++17 standard
Keeps isatty()=True for console TUI apps without showing console.
Shows console for GUI apps. For example starting headless-tty.exe -- notepad will show a console (empty).
2.0.0
Second release
Uses c++23
Comes with helper binary messenger.exe with authentication pipeline built in (NO server file, but pseudocode present in Readme.md)
Added truly headless mode
Keeps console hidden for GUI apps for example headless-tty.exe -- notepad shows no console
Keeps console hidden for console apps while keeping isatty()=True, for example headless-tty.exe -- claude shows no console but keeps claude code CLI INK app hidden in interactive mode and isatty()=True (or else it would have crashed)
Replaced deprecated call
Bidirectional process termination
Killing headless-tty kills the child process (Job Object)
Closing/killing the child process causes headless-tty to exit cleanly (Monitor Thread)
Comes with an example showcase file written in python usage_example.py to help showcase the Software's potential.
2.5.0
System Tray Mode
Added --sys-tray flag for running processes with a system tray icon
Right-click tray icon to show/hide console on demand
Console supports full color output and VT sequences
Closing console window exits the application
Child process exit automatically removes tray icon
Help message now displays when running with -h or --help from command line
Install Headless-TTY using WinGet - wingetCollections