agwinterm
August 13, 2026 ยท View on GitHub
agwinterm
A native Windows terminal built for AI coding agents.
Workspaces, sessions, splits, live agent-status, a scriptable control API, full screen-reader accessibility, and OS default-terminal integration โ in a fast, custom-drawn Win32 + Direct2D shell. A Windows homage to umputun's agterm.
๐ Kudos to umputun and agterm
agwinterm exists because of umputun and his terminal agterm. agterm's design โ a terminal that treats AI coding agents as first-class citizens, with per-session status, a sidebar of workspaces, a quick terminal, and a language-agnostic control socket โ is the blueprint this project follows on Windows.
This is an independent, from-scratch implementation written in C# on a native Win32/Direct2D stack (agterm is Swift on libghostty); no agterm code is used. It is a tribute and a port of the ideas/UX, built so Windows users can have the same agent-first workflow. If you're on macOS, go use the real thing: github.com/umputun/agterm. Thank you, umputun. ๐
Highlights
Agent-first
- Workspaces โ sessions โ panes in a custom-drawn sidebar (drag-reorder, rename, flag, focus,
unread badges, multi-select with Ctrl/Shift+click for batch flag / move / close, reopen closed
sessions and workspaces with
Ctrl+Shift+R). - Dashboard (
Ctrl+Shift+D): a grid of live session previews โ arrow-navigate, Enter or double-click to jump in; or drive it withagwintermctl dashboard <ids>. - Agent status per session (idle / active / blocked / completed) as a colored dot + title-bar
bell โ driven by your agent via hooks or the control API, with blink, auto-reset, and sounds.
Run
agwintermctl install hooks(or the palette entry) once to wire Claude Code / Codex up. - Claude Code session binding & auto-resume: the same installer adds a transparent
claudewrapper (active only inside agwinterm) that ties Claude's session id to the agwinterm pane. You just typeclaudeโ a fresh pane starts a bound session, and a pane that already has a transcript resumes it. On restart, agwinterm re-launches each bound pane and the conversation comes back โ no guessing which shell was which. Already had Claude running before installing this? Runagwintermctl claude adopt(or palette โ Make Claude Sessions Resumable) once to bind your existing conversations to their panes. - Update Claude Code (palette โ Update Claude Code, or
agwintermctl claude update): agwinterm quietly notices when a new Claude Code ships (npm registry;claude-update-check = falseto opt out), then โ on your command โ runsclaude updatein an overlay terminal and restarts every running Claude session, each resuming its own conversation (YOLO panes stay YOLO). - agwinterm self-update (palette โ Update agwinterm, or
agwintermctl app update): notices new releases on GitHub (update-check = falseto opt out), then โ on your command โ downloads the right artifact for your install (installer or portable exe), verifies its SHA-256 against the release digest, restarts, and your sessions restore. scoop/chocolatey installs are never touched โ the hint points at your package manager instead. - Shells that survive the UI (EXPERIMENTAL): flip Settings โ General โ Session host to
Pty-host server (
session-host = server) and your sessions live in a tiny headless process โ quit, self-update, or even crash the UI and every shell (including a running Claude conversation) keeps running; the next start reattaches each pane to its live session, same process, same state. Closing a pane still closes its shell. The tmux idea, native on Windows. - A scriptable control API:
agwintermctl(or newline-JSON over a named pipe) โ any language can drive it, including full read-back (tree with split ratios + pane ids, window state, session output). Opt-in installers for the agent skill and Claude Code / Codex status hooks. - Splits (a split collapses to the survivor when a pane exits), scratch & quick terminals, ephemeral overlays (open/resize/close via API), multi-window with per-window addressing.
A real Windows terminal
- Default Terminal Application: register agwinterm from Settings โ General (per-user, no admin,
one-click revert) and every console app you launch โ
cmdfrom Win+R, double-clicked.exes โ opens as an agwinterm session via the ConPTY handoff, titled by the app. - Fast: sustained output at ~33k lines/s (~87 % of a bare conhost window), 0.4 s cold start,
~0 % idle CPU, and a leak-hunted session lifecycle (
tools/profile-memory.ps1keeps it honest). - Sixel + Kitty graphics, win32-input-mode + Kitty keyboard protocol, ligatures (toggleable), builtin box-drawing glyphs, buffer restore, block selection + keyboard mark mode, read-only panes, elevated & de-elevated sessions side by side (โก marker), FTCS/OSC-133 prompt marks with jump-to-prompt, taskbar progress (OSC 9;4).
- Shells are launched with
TERM_PROGRAM=agwinterm(+ the usualAGWINTERM_*vars), so prompt engines, tmux and scripts can detect the host terminal.
Accessible โ screen readers are first-class
- The terminal is a UIA text document: Narrator/NVDA read it line by line, track the caret (tight one-cell focus box), and new output is announced automatically after it settles.
- Everything is in the UIA tree: sessions, every chrome button, settings tabs and controls โ scannable (Caps Lock + arrows), focusable, and invokable. Dialogs are modally scoped so the reader can't wander behind them.
- F6 moves keyboard focus between the terminal and the session list (arrows + Enter there); the Settings dialog is fully keyboard-navigable โ Tab reaches the tab headers too (Enter switches), with a classic keyboard-only dotted focus rectangle; buttons speak on hover and show tooltips.
- F1 help lists the effective keybindings and โ when a reader is attached โ speaks a spoken orientation guide for low-vision users.
Looks & feel
- Whole-window theming with ~580 bundled themes (the ghostty / iTerm2 set) โ sidebar, title bar, and terminal retint together. Fonts apply live from Settings, and there's an optional follow Windows light/dark mode that swaps between a light and a dark theme you choose.
- Configurable sidebar font size, sidebar tint, window opacity, and inactive-pane muting.
- cwd in the title out of the box (composes with oh-my-posh) + an oh-my-posh theme picker.
- Toolbar modes (normal / compact / hidden full-bleed), window opacity, unfocused dim, per-session background watermarks.
- MRU
Ctrl+Tabswitcher, fuzzy command / session / action palettes, search, tmux-style leader chords, custom commands with{AGW_*}tokens and run modes.
Install
Grab either from the Releases page:
agwinterm-setup-<version>.exeโ per-user installer (Start-menu shortcut + uninstaller).agwinterm-portable-<version>-win-x64.exeโ portable: a single self-contained exe, no installation โ run it from anywhere (settings still live under%LOCALAPPDATA%\agwinterm).
Both are self-contained (no .NET runtime needed) and need no admin rights.
Or install from a package manager (both use the release artifacts and self-update on new releases):
# Scoop (portable build)
scoop bucket add agwinterm https://github.com/yeroo/scoop-bucket
scoop install agwinterm
# winget
winget install yeroo.agwinterm
# Chocolatey (portable build)
choco install agwinterm
- Binaries are currently unsigned, so SmartScreen will warn on first run โ More info โ Run
anyway. Release artifacts carry Sigstore build-provenance attestations โ verify with
gh attestation verify <file> --repo yeroo/agwinterm. - The installer is deliberately minimal (copies files + shortcuts). The integrations
(put
agwintermctlon PATH, agent status hooks, agent skill, shell integration, default-terminal registration) are opt-in from inside the app โ command palette (Ctrl+Shift+P) or Settings.
Build from source
Requires the .NET 10 SDK (see global.json) on Windows x64.
# build + test
dotnet build Agwinterm.slnx -c Release
dotnet test Agwinterm.slnx -c Release
# run the app
dotnet run --project src/Agwinterm.Win32 -c Release
# build the installer (needs Inno Setup 6)
./installer/build.ps1
# build the portable single-file exe (no Inno Setup needed)
./installer/build-portable.ps1
Dev builds run side-by-side with the installed release
A Debug build uses a separate instance identity, agwinterm-dev, so it keeps its own data
dir (%LOCALAPPDATA%\agwinterm-dev: config, sessions, keymap, themes) and its own control pipe โ
it never touches, or fights over the pipe with, your installed Release (agwinterm). So you can
daily-drive the release and run dev builds at the same time.
dotnet run --project src/Agwinterm.Win32 # Debug -> the "agwinterm-dev" instance
agwintermctl --pipe agwinterm-dev tree # drive the dev instance from outside
agwintermctl tree # (default pipe) drives the release
Inside any session, AGWINTERM_PIPE is already set, so a bare agwintermctl auto-targets the
instance it's running in. Force a specific identity with --app-id <name> or AGWINTERM_APP_ID
(handy for a second throwaway instance). Dev builds also skip registering as the default-terminal
COM server, so they never intercept the release's console handoffs.
Control it from anything (agwintermctl)
agwinterm is scriptable through a local named pipe speaking newline-delimited JSON, with
agwintermctl as the CLI wrapper. A few examples:
agwintermctl tree --json # workspace/session tree (+ splits, badges, overlays)
agwintermctl window state # sidebar/fullscreen/active read-back
agwintermctl session status blocked --sound # report agent status (a dot + bell in the UI)
agwintermctl session new --name build --workspace-name CI --create-workspace
agwintermctl session type "npm test`n" # type into the active session
agwintermctl session overlay open "git diff" --size-percent 60
agwintermctl dashboard build test deploy # grid overview of chosen sessions
agwintermctl theme set "Tokyo Night" # retint the whole window
agwintermctl window new --name scratchpad # open a second window
Inside a session you get AGWINTERM_SESSION_ID, AGWINTERM_WINDOW_ID, and AGWINTERM_PIPE.
Run agwintermctl install skill (or the palette entry) to teach Claude Code / Codex the full verb set.
agwinterm-lite โ the tiny client
agwinterm-lite is a second, minimal client for old or low-RAM machines: a single small
C++ exe (Win32/WTL, no .NET) over the same Rust emulator core and pty-host. It trades the
custom-drawn chrome for real native controls โ menu bar, toolbar, TreeView sidebar,
status bar โ in the classic Windows look.
- Themes: Dark / Light / Classic / Auto (follows Windows) from File โ Properties. Dark covers everything โ menus, toolbar, sidebar, dialogs, scrollbars, title bar. Classic keeps the authentic raised-3D, pre-theme look.
- Agent workflow: per-session agent status (bold = blocked, italic = working), an attention bell that lights amber and jumps to the next blocked session, flagged sessions with a flagged-only view, unread badges (commands finished while a session was off-screen), workspace focus, sidebar drag & drop.
- Terminals: workspaces + sessions with restore, a 2-pane split, quick / scratch / overlay popup terminals, font catalog (incl. bundled Cozette, Tamzen, Terminus, Spleen, UNSCII & GNU Unifont bitmap fonts) โ face and size are chosen once in Properties; there is deliberately no zoom, because a raster face only exists at the strike sizes its pack ships, MS-DOS/EGA palette, cmd.exe-style Properties dialog, fully rebindable keys (all unbound by default โ keystrokes belong to your shell).
- Scriptable: the same newline-JSON control pipe, speaking the
agwintermctldialect โ 38 verbs covering sessions, workspaces, windows, and the tree (agwintermctl --pipe agwinterm-lite tree). Shells getAGWINTERM_*env, so hooks and the agent skill work. - Multi-window, the lite way: every window is its own tiny process (
--pipe <name>), all sharing one pty-host;agwintermctl window new/list/select/...drives them. - CLI:
-p/--profile,-d/--dir,--maximized,--no-restore,--pipeโ the full app's flag names โ plus--diagnose(see below). - Explains itself: lite keeps a small always-on log of its own decisions โ session saves and
restores (with counts, byte totals, and the exact error when a write fails), focus handoffs, and
font/pack resolution โ at
%LOCALAPPDATA%\agwinterm-lite\lite.log(lite-<instance>.logfor named instances), rotating at ~1 MB into.log.old. It records what lite did, never terminal output, pasted text, or your command lines, so it's safe to attach to an issue.
Grab agwinterm-lite-setup-<version>.exe from Releases (per-user, no admin), or build it
(needs the VC++ ATL component): ./lite/build.ps1 (dev build) / ./installer/build-lite.ps1 (setup).
Session restore & the state file
Lite saves its workspaces and sessions whenever the tree changes and on exit, and rebuilds them on
the next launch (--no-restore starts empty instead). Everything about that is on disk and readable:
- One state file per instance.
%LOCALAPPDATA%\agwinterm-lite\sessions.tsvfor the default instance,sessions-<instance>.tsvfor a named one. Because every lite window is its own process, each window restores only its own sessions โ sessions you created in--pipe workcome back in--pipe work, never in the default window. That is the mundane reading of "my sessions are gone": right sessions, wrong window. - Format: tab-separated UTF-8 text,
V1header, one record per line โWworkspace,Ssession (workspace index, name, app, cwd, then args),Fflagged indices,Dhost session ids,Aactive workspace. The format grows by adding line types, so a file written by an older build still restores, a line type this build doesn't write is still honoured when it finds one (O, focused workspace), and a file from a newer build is read for the lines this one knows rather than thrown away. - What is saved: the visible sessions, each with its live working directory (read from the
shell process, so it follows you as you
cd). Split-pane shells are hidden and deliberately not persisted โ the split comes back as a single pane. - Writes are atomic, and keep one generation. The save writes
sessions.tsv.tmp, rotates the current file tosessions.tsv.bak, then renames the temp over the target โ a crash or a full disk mid-write can no longer leave a truncated file where a good one was. A zero-session save is refused over a populated file (and says so in the log); the one legitimate empty is you closing the last session, which also deletes the.bakso what you just closed doesn't come back. - Restore order:
sessions.tsvโ.bakif the primary is missing, empty, or parses to zero sessions โ a fresh window. If the pty-host still holds the shells โ lite was killed or the machine was shut down rather than closed โ those shells are still running and get adopted live instead of relaunched. An adopted shell keeps everything it was running, and lite asks it to redraw, so the screen comes back โ but the scrollback does not: lite re-attaches to the live process with a fresh emulator, so only what is on screen is repainted, not the history the old window had. A shell that has already exited, or one another window is currently driving, is not adopted โ it is relaunched (or left to its owner) instead. - Recovering by hand.
--no-restorestarts empty, and the next save publishes that oversessions.tsvโ but the generation you wanted survives assessions.tsv.bak. Copy the.baksomewhere safe first: only one generation is kept, so the next save of the window you are looking at overwrites it in turn. Then close lite, copy your saved file oversessions.tsv, and relaunch.--diagnoseprints both files with their sizes (and the primary's contents), so you can tell which one holds your sessions before you copy anything. - "Restart everything" (File โ Restart everything) relaunches the same instance โ it carries
this window's
--pipe <name>over, so it comes back reading the same state file.--diagnoseprints the exact command line it would use. - A spec that won't start on this machine (a profile whose exe only exists on your other PC, a
cwd on an unmounted drive) stays in the tree as a
(failed to start)entry with a note in its pane, rather than silently vanishing. Its name, workspace, cwd and args are kept and re-saved, so it starts normally again on the machine that has the app. Scripts can spot one without reading the log:agwintermctl tree --jsonreports"failed"and"exited"per session.
Every one of those branches names itself in lite.log, and lite/test/restore-matrix.ps1 drives the
whole matrix โ kill vs. graceful close, two windows at once, interrupted writes, .bak fallback,
bogus apps, old and future file formats โ as regression cover.
If lite exits at startup with "pty-host did not become usable", a previous agwinterm-ptyhost.exe
is wedged: end it in Task Manager and relaunch. lite.log records the connection attempt by attempt,
including the case it is really there for โ a host left dying by a killed window, which answers a
handshake for a moment while refusing every real command.
Reporting a lite problem
Run agwinterm-lite --diagnose and attach its output plus lite.log. The report is read-only and
safe to run while lite is open; it prints the state file's path, whether that directory is genuinely
writable (a real write probe, which is what catches a redirected or policy-locked profile), the state
file's contents and its .bak generation, the resolved font, and the bundled pack inventory:
> agwinterm-lite --diagnose
version: 0.17.2
instance: (default)
restart cmdline: "C:\Users\you\AppData\Local\Programs\agwinterm\agwinterm-lite.exe"
state
dir: C:\Users\you\AppData\Local\agwinterm-lite
dir writable: yes
session file: ...\sessions.tsv
size: 59 bytes
modified: 2026-07-31 20:14:02
backup file: ...\sessions.tsv.bak (59 bytes)
Use --pipe <instance> --diagnose to ask about a named instance โ it reports that instance's
state file, which is the one its window restores from.
Keyboard essentials
| Key | Action |
|---|---|
F1 | Help (effective keybindings + accessibility guide) |
F6 | Move focus terminal โ session list |
Ctrl+Shift+D | Dashboard โ grid of live sessions |
Ctrl+Shift+T / Ctrl+Shift+R | New session / reopen closed |
Ctrl+Tab | MRU session switcher |
Ctrl+D | Split pane ยท Ctrl+` quick terminal ยท Ctrl+J scratch |
Ctrl+Shift+P | Action palette |
F11 | Fullscreen |
Everything is rebindable in keymap.conf (see F1 for the live list).
Configuration
%LOCALAPPDATA%\agwinterm\agwinterm.confโ appearance & behavior (also editable in Settings).%LOCALAPPDATA%\agwinterm\keymap.confโ keybindings + custom commands + leader chords.- Themes: the bundled set ships with the app; drop extra ghostty-format
*.conffiles in%LOCALAPPDATA%\agwinterm\themes\.
Acknowledgements
- umputun / agterm โ the original and the inspiration for every bit of this project's UX. ๐
- Ghostty & iTerm2-Color-Schemes โ the bundled color themes are the community ghostty/iTerm2 set.
- Vortice.Windows (Direct2D/DirectWrite), Porta.Pty (ConPTY), and microsoft/terminal's OpenConsole for the default-terminal handoff.
License
MIT ยฉ 2026 Boris Kudriashov. Bundled theme files retain their upstream (iTerm2-Color-Schemes, MIT) licensing.