A GPU-native macOS terminal emulator built for two audiences: humans who never want to see a frozen terminal again, and agents/LLMs, which get a first-class machine interface to the live terminal instead of screen-scraping.
Pure Swift + Metal. No Electron, no web view, no frameworks beyond the OS.
On an Apple Silicon MacBook (M3), release build:
catof a 18.8 MB / 2,000,000-line file: 0.95 s (~20 MB/s, ~2.1M lines/s)- UI stays fully responsive during output floods (input, scrolling, and the control socket all keep working — verified during the benchmark)
- Idle cost: zero GPU frames, and the render display link pauses itself after ~1 s of no output, so idle CPU is zero too
The design rule: the main thread never touches bytes.
| Thread | QoS | Job |
|---|---|---|
| PTY read | .userInitiated (P-core) |
256 KB batched reads + VT parsing |
| Render | .userInteractive (P-core) |
CADisplayLink → Metal encode |
| PTY write | .userInitiated serial queue |
keystrokes/paste, never blocks UI |
| Control socket | .utility (E-core) |
agent queries |
| Main | — | input events + window management only |
QoS classes are how macOS decides P-core vs E-core placement on Apple Silicon: latency-critical work lands on performance cores, the agent control plane on efficiency cores.
Shared state is one cell grid behind a single os_unfair_lock, held for
microseconds: the parser applies whole kernel-buffer batches, the renderer
snapshots rows by reference (copy-on-write does the rest). Nothing waits on
anything slow while holding it.
- Parsing: single-pass VT state machine with a bulk fast path — printable ASCII runs are blitted straight into row memory, skipping the state machine per byte. 16-byte POD cells in an optimized release build.
- Rendering: instanced Metal quads — one draw call for run-merged
background rects, one for glyphs, one for decorations. Glyphs rasterize once
(CoreText, device-pixel scale) into an R8 shelf-packed atlas; after warmup a
frame is just three instanced draws. Triple-buffered,
framebufferOnly, opaque layer. - Damage gating: frames render only when the terminal generation counter changes. A flood of output coalesces to the display's refresh rate; the parser never waits for the GPU.
- Resize: during live resize infinitty switches to synchronous
presentsWithTransactionpresentation, so content stays glued to the window edge — no jelly, no white flash. - Scrolling: rows are reference-swapped, not copied; scrollback is a ring of 10,000 rows.
infinitty exposes a control socket at $INFINITTY_SOCKET (exported to the shell,
0600, one command per connection, newline-terminated):
printf 'screen\n' | nc -U "$INFINITTY_SOCKET" # visible screen as text
printf 'history 500\n' | nc -U "$INFINITTY_SOCKET" # last N lines incl. scrollback
printf 'last-output\n' | nc -U "$INFINITTY_SOCKET" # output of last command *
printf 'last-command\n' | nc -U "$INFINITTY_SOCKET" # the command line itself *
printf 'exit-code\n' | nc -U "$INFINITTY_SOCKET" # its exit code *
printf 'send-line ls\n' | nc -U "$INFINITTY_SOCKET" # type into the terminal
printf 'send text\n' | nc -U "$INFINITTY_SOCKET" # type without return
* needs OSC 133 semantic prompts — source shell-integration/infinitty.zsh
from your ~/.zshrc. infinitty parses the markers (prompt start, input start,
output start, exit) and tracks command regions by absolute line number, so
"give me exactly the last command's output" is an O(1) lookup, not a heuristic.
An agent driving a shell can therefore: run a command, wait, read precisely its output and exit code, and never parse ANSI soup. That's the interface a model wants.
Every pane exports who it is, so a child process can address the pane it is running in without guessing:
| Variable | Value |
|---|---|
INFINITTY_SOCKET |
this pane's control socket (TITERM_SOCKET is the legacy alias) |
INFINITTY_PANE_ID |
this pane's id, as taken by the app-level API below |
INFINITTY_APP_SOCKET |
the app-level socket for this infinitty process |
That matters one hop away. $INFINITTY_SOCKET is an AF_UNIX path on this
machine, so an agent running over SSH cannot reach it — but forward either
socket and the pane id travels as a plain string, and the remote process can
address this exact pane:
ssh -R /tmp/infinitty.sock:"$INFINITTY_APP_SOCKET" \
-o SendEnv=INFINITTY_PANE_ID you@remote # AcceptEnv on the far sideOne socket per infinitty process, discoverable at /tmp/infinitty-current.sock:
printf 'list\n' | nc -U /tmp/infinitty-current.sock # panes as JSON
printf 'run 1 make test\n' | nc -U /tmp/infinitty-current.sock # sync: {"exitCode":0,"output":…}
printf 'new-tab\n' | nc -U /tmp/infinitty-current.sock # returns new pane id
printf 'split 1 right\n' | nc -U /tmp/infinitty-current.sock
printf 'toggle-quick-terminal\n' | nc -U /tmp/infinitty-current.sock
printf 'focus 2\n' | nc -U /tmp/infinitty-current.sock
printf 'activity deploying…\n' | nc -U /tmp/infinitty-current.sock # post to the notch widget
printf 'toggle-sidebar\n' | nc -U /tmp/infinitty-current.sock # show/hide the Files pane
printf 'sidebar show\n' | nc -U /tmp/infinitty-current.sock # show|hide|toggle the Files pane
printf 'sidebar-tab chat\n' | nc -U /tmp/infinitty-current.sock # open/focus Files, Changes, or Chat
printf 'chat-model claude\n' | nc -U /tmp/infinitty-current.sock # set chat model (name/substring)
printf 'chat-effort high\n' | nc -U /tmp/infinitty-current.sock # set effort: auto|low|medium|high
printf 'subscribe\n' | nc -U /tmp/infinitty-current.sock # JSON event stream
Plus per-pane proxies (send, send-line, screen, history,
last-output, last-command, exit-code — all <cmd> <pane-id> …).
subscribe streams pane-opened, pane-closed, title, and marker
events as JSON lines. Socket-driven input lights the agent glow.
Every Chat pane has a stable process-local identity (Chat 1, Chat 2, …).
Drag the circular connector in one pane header onto another pane's connector to
create or join a Channel. Linked headers show a visible badge such as
Channel 1 · 2, not color alone. Clicking the badge opens a compact membership
summary; Channel is durable coordination state, not a separate pane. Chats,
terminals, Files, Browser, and agent surfaces can all join the same room while
remaining focused interfaces for their own work.
For linked Chat panes, Channel membership is part of every real provider turn: the agent receives its own participant name, the Channel name, exact peer names and roles, authoritative responsibility scopes and plan ownership, and the bounded recent Channel transcript. Accepted user prompts and provider responses are appended to the durable room log, so a peer's next turn can read and reference the handoff. Amp participates through the same provider-facing context path; it is not the headless host. Renames update the stored participant identity, closing a pane removes it from every peer's live membership, and long provider replies are explicitly bounded for the shared transcript without shortening the local answer.
Agents launched inside ordinary terminal panes use the pane's authenticated
$INFINITTY_SOCKET rather than pretending they are Infinitty-owned Chat panes.
The bundled MCP server automatically registers recognized Claude Code, Codex,
Amp, OpenCode, Gemini, Grok, Aider, Qwen, Copilot, Cursor, Droid, Kimi, and
Goose clients as unique participants such as Claude 1 and Amp 2. MCP
initialization now includes the first bounded live
Channel snapshot, so an agent does not need a discovery/tool round before it
knows its room. infinitty_channel_self remains available for explicit refresh
and verification; infinitty_channel_post publishes with the author and
Channel derived server-side from the owning pane. A caller cannot select
another pane or forge an author ID. MCP registrations are bound to the
server-observed MCP process; normal exit unregisters immediately, forced
termination is reaped by the host, and an older MCP process cannot unregister a
newer owner of the same pane.
For per-turn context without model tool selection, enable Install Channel
context hooks in Infinitty settings (or set mcp-auto-register = true). The
toggle is off by default because enabling it edits user configuration files.
The initial MCP snapshot and the explicit wrapper do not require the toggle;
native per-turn hooks do. Enabling it merges Claude SessionStart and
UserPromptSubmit hooks without overwriting existing hooks. The
provider-neutral infinitty-agent edge can wrap any CLI:
infinitty-agent run -- claude
infinitty-agent run --provider amp -- ampWhen shell-integration/infinitty.zsh is sourced inside an Infinitty pane,
recognized CLIs are wrapped automatically. Set
INFINITTY_AGENT_AUTO_WRAP=0 to disable that behavior. The wrapper registers
the process and preserves its PID/TTY; the context helper is also usable from
provider plugins or hooks:
infinitty-agent context --format plain
infinitty-agent context --format claude --event UserPromptSubmitEven without the shell wrapper, the visual host registers a recognized foreground CLI as soon as it appears in a pane. The wrapper remains the provider-neutral path for headless sessions and CLIs that Infinitty does not recognize by process name.
The same provider-neutral bridge is available to any local process through the pane socket. Membership is queried dynamically, so joining or leaving a Channel after the process starts works without stale environment variables:
printf 'channel-context\n' | nc -U "$INFINITTY_SOCKET"
# register/post payloads are versioned base64url JSON
register=$(printf '%s' '{"v":1,"displayName":"Ada","role":"reviewer","provider":"claude"}' \
| openssl base64 -A | tr '+/' '-_' | tr -d '=')
printf 'channel-register %s\n' "$register" | nc -U "$INFINITTY_SOCKET"
message=$(printf '%s' '{"v":1,"text":"Review complete."}' \
| openssl base64 -A | tr '+/' '-_' | tr -d '=')
printf 'channel-post %s\n' "$message" | nc -U "$INFINITTY_SOCKET"Set INFINITTY_AGENT_NAME, INFINITTY_AGENT_ROLE,
INFINITTY_AGENT_PROVIDER, INFINITTY_AGENT_MODEL, or
INFINITTY_AGENT_SESSION_ID before launching an MCP-backed agent to override
automatic metadata. Provider and model identifiers remain opaque strings.
Visual and headless processes use one local Channel coordinator and one
tamper-evident journal. Linking endpoints from different Infinitty instances
therefore updates the same authoritative room; live instances receive the new
projection, and another process replays the journal and takes over if the
coordinator owner exits. infinitty_channel_panel is retained as a compatible
room-data adapter for list/snapshot/message/role operations; it no longer opens,
focuses, or closes a visual Channel pane.
The local audit surface is independently queryable through
infinitty_audit_query. Pages contain bounded summaries, are pinned to the
first page's verified journal tip, and are returned only after both hash-chain
verification and fail-closed semantic replay. infinitty_audit_export writes
the complete canonical journal to an app-owned private JSONL artifact and signs
it with Infinitty's local Ed25519 authority; callers cannot choose a destination
path. infinitty_audit_verify rechecks the signature, payload digest, manifest,
record chain, and semantic replay without invoking provider, pane, worktree, or
other external effects. Rejected and malformed control requests are also
hash-chained with a bounded reason and SHA-256 request fingerprint without
changing room state. This is a verified local audit export, not a claim of WORM
retention or third-party compliance certification.
A room agent with runtime: "cloud" must include an approved
cloudConnection. Infinitty never accepts a raw token in a proposal and never
falls back to a local CLI. The proposal contains only a credential-free
endpoint and the name of an uppercase environment variable already present in
the addressed visual or headless host. remoteWorkspace is the absolute
checkout or mounted-worktree path as seen by the remote runtime; Infinitty binds
it into the human-approved digest and never pretends that a local path is
automatically mounted remotely:
{
"runtime": "cloud",
"provider": "codex",
"modelID": "provider-owned-opaque-model-id",
"cloudConnection": {
"endpointURL": "wss://codex-host.example/app-server",
"credentialEnvironmentVariable": "CODEX_CHANNEL_TOKEN",
"authentication": "bearer",
"remoteWorkspace": "/srv/worktrees/channel-release/architect"
}
}Codex uses the app-server initialize / thread/start or thread/resume /
turn/start protocol, streams item/agentMessage/delta, and targets
turn/interrupt on cancellation. The approved worktree path and opaque model
identifier are sent unchanged. The upstream WebSocket app-server transport is
currently experimental, so deployments must explicitly provide an
authenticated WSS endpoint; Infinitty does not open or expose a listener.
Claude uses the Managed Agents API with an HTTPS endpoint, agentID,
environmentID, optional vaultIDs, and either api_key or bearer
authentication. It opens the SSE stream before posting user.message, treats
preview deltas as provisional, commits only authoritative agent.message
events, sends user.interrupt on cancellation, and stops for explicit human
handling when the session reports requires_action. Managed Agents is an
upstream beta surface.
Before either agent can run, Infinitty authenticates and prepares the remote
session, writes a provider/session/workspace receipt into the tamper-evident
Channel journal, and only then transitions the proposal to running. Restarted
headless hosts resume that exact remote session from the durable receipt.
Authentication, protocol, timeout, or remote failures appear in the Chat as
System failures and are never recorded as successful agent responses. When
the Chat is connected, the failure is also journaled as a system-authored
Channel message so peers and audit consumers can distinguish it from agent
work.
Run Infinitty as a genuine windowless terminal host:
infinitty --headless ~/Documents/GitHub/myrepoThis path does not create NSApplication, windows, Metal layers, renderers, or
display links. It still launches real PTYs and exposes instance discovery,
per-terminal sockets, the app control socket, Channel state, event
subscriptions, and the same MCP terminal/Channel tools. UI-only operations such
as browser panes return an explicit unavailable error.
Amp remains an agent provider. Its non-interactive provider transport is separate from Infinitty's headless app mode.
infinitty-mcp (built alongside the app) exposes all of it as MCP tools —
infinitty_run, infinitty_list_panes, infinitty_screen, infinitty_send,
infinitty_split, infinitty_channel_self, infinitty_channel_post,
infinitty_activity, and more:
# Installed npm/tarball release:
claude mcp add infinitty -- infinitty-mcp
# Source checkout:
swift build -c release
claude mcp add infinitty -- /path/to/titerm-agent-channels/.build/out/Products/Release/infinitty-mcpThe signed app, release tarball, and npm package include both
infinitty-mcp and the provider-neutral infinitty-agent helper.
infinitty_run is the headline: it types the command, waits for the OSC 133
done-marker, and returns {"exitCode": …, "output": …} in one tool call.
infinitty_list_panes returns stable numeric terminal ids and string handles
for Chat, Files, Browser, and agent-surface panes. The same handle works with
infinitty_focus, infinitty_split, infinitty_close, and event filtering in
both visual and headless instances.
infinitty takes a folder argument — the shell starts there:
infinitty ~/Documents/GitHub/myrepoIf infinitty is already running, the folder opens as a new tab in the
current window (instant — the argument is forwarded over the control
socket). open -a Infinitty <folder> and dropping a folder on the Dock
icon do the same. The socket commands take an optional directory too:
new-tab [dir], new-window [dir].
GitHub Desktop — Settings → Integrations → Shell → Configure Custom Shell…:
- Path:
/Applications/Infinitty.app/Contents/MacOS/infinitty— the binary inside the bundle; Desktop rejects the.appitself as "not a valid executable" - Arguments:
%TARGET_PATH%
npm install -g @jasonkneen/infinitty # downloads release binaries
infinittyOr grab the tarball from GitHub Releases.
swift build -c release
.build/out/Products/Release/infinittySigned + notarized releases are cut locally with one command; the full process, one-time setup, and cert-recovery steps are in RELEASING.md.
swift build -c release --arch arm64 --arch x86_64
./scripts/ship-signed.sh X.Y.ZRequires macOS 14+ and Xcode command line tools. $SHELL is spawned as a
login shell with TERM=xterm-256color and COLORTERM=truecolor.
Config file at ~/.config/infinitty/infinitty.conf (or ~/.infinitty.conf), see
infinitty.conf.example:
font = Berkeley Mono # any installed font (default: SF Mono)
font-style = Thin # face style: Thin, Light, Medium, SemiBold, ...
font-thicken = false # ghostty-style stroke thickening
font-size = 13 # terminal glyphs, points
# interface-font-size = 15 # Chat + ShadKit UI; also available in Settings
margin = 8 # window content margin, points
line-spacing = 1.0 # line height multiplier
kerning = 1.0 # cell width multiplier (letter spacing)
foreground = #D7DAE0 # hex or basic color names
background = #0F1216
cursor-color = #AEB8C4Live reload: the config file is watched — edits apply to every open pane
within ~150 ms. Also ⌘R, or printf 'reload\n' | nc -U "$INFINITTY_SOCKET".
Ghostty compatibility: Ghostty key names work (font-family,
window-padding-x/y, adjust-cell-width / adjust-cell-height in % or
pixels, font-thicken), and if no infinitty config exists, the keys infinitty
understands are read from ~/.config/ghostty/config automatically.
Environment variables override everything: INFINITTY_FONT, INFINITTY_FONT_SIZE,
INFINITTY_MARGIN, INFINITTY_LINE_SPACING, INFINITTY_KERNING; INFINITTY_CONFIG
points at an alternate config file.
Works out of the box: icon/powerline glyphs (Private Use Area) resolve
through a fallback chain of installed Nerd Fonts even when the primary font
lacks them, icons are centered in their cell, and powerline separators
(U+E0B0–U+E0BF) are stretched from their outlines to fill the cell exactly —
seamless prompt segments at any line spacing. Set INFINITTY_FONT to a Nerd
Font to use one everywhere.
- Tabs: native macOS tabs — ⌘T new tab, ⇧⌘←/→ previous/next tab, ⌘1–8 select by position, ⌘9 selects the last tab, and the tab bar "+" works; hold ⌘ to reveal the numbers in tab titles; ⇧⌘T renames the active tab
- Splits: ⌘D split right, ⇧⌘D split down; right-click either split button to choose Terminal, Files, Chat, or Browser. Splits nest arbitrarily; Files contains its own Files/Changes switch, and each main tab can keep one Files pane plus multiple independently named Chat panes alongside any number of terminals. ⌘W closes the focused pane (the tab closes when its last terminal exits); ⇧⌥←/→/↑/↓ focuses and briefly highlights the nearest pane in that direction; hold ⇧⌥ to reveal pane numbers and the focused-pane outline, then press ⇧⌥1–9 to focus directly. Every pane has compact split controls in its header. Drag a pane header onto another pane's edge to move it, or onto the center to swap them; the blue preview shows the landing region before anything moves. Double-click a pane header or press ⇧⌘Return to zoom it, then repeat to restore the exact split tree.
- Quick terminal: set
quick-terminal-key = cmd+shift+spacefor a persistent, global Quake-style terminal that slides down from the top. Its shell, scrollback, splits, and internal tabs stay alive while hidden. Its always-visible tab strip supports the+and selected-tab close buttons, ⌘T, ⇧⌘←/→, and ⌘1–9. It can autohide on focus loss and target the main screen, mouse screen, or menu-bar screen. The global key uses the macOS hot-key API and does not require Accessibility permission. Drag its bottom edge to resize it; the height is remembered across launches. ⌘W closes the focused pane (and then its tab when the last pane exits); ⇧⌘W hides the panel without terminating any panes. - Mouse reporting: click/drag/motion/scroll forwarded to apps that ask (vim, tmux, htop, lazygit; modes 9/1000/1002/1003, SGR + legacy encoding). Hold Shift to scroll local scrollback or select while an app owns the mouse.
- Selection & copy: drag to select (scrollback-stable), double-click for word, triple-click for line, ⌘C copies
- Links: hold ⌘ and hover to highlight URLs; ⌘-click opens them
- Drag & drop: files dropped on the window insert shell-escaped paths; dropped text pastes (bracketed when apps ask)
- Shift+Enter sends CSI-u
13;2u— newline-without-submit in Claude Code and other modern TUIs - Inline images: both major protocols — iTerm2 OSC 1337
File=and the kitty graphics protocol (chunked base64, PNG + raw RGB/RGBA incl. zlib, direct & temp-file transmission, transmit/put/query/delete with protocol responses sokitten icat, yazi, chafa detect support). Images scroll with content and live in scrollback. - Window dragging is titlebar-only; drags in the grid always select. Right-click for context menu: copy/paste, 4-way splits, rename tab, reset.
- Rename a tab: press ⇧⌘T, choose Rename Tab… in the terminal-content right-click menu, or double-click its tab title (double-click the window title when there is no visible tab strip). Quick-terminal titles become editable directly in their tab; native tabs use an anchored rename popover. ⏎ commits. Clicking away also commits a quick-terminal rename; for a native tab, clicking elsewhere in the same terminal window commits while clicking outside it cancels. ⎋ or pressing ⇧⌘T again cancels and discards any typed text; an empty name restores the automatic title. Custom names are kept in memory for the lifetime of their native or quick-terminal tab; they are not restored after the tab closes or the app restarts.
- Running-process indicator (native titlebar): the active pane's
foreground process (e.g.
vim,node,Safari) is shown as the window subtitle and as a small icon in the trailing edge of the titlebar. The icon updates within ~2 s of a command starting/ending, or instantly when OSC 133C/Dmarkers fire. ⌃-click the icon to refocus the pane it describes after a tab switch. - ⌘-click a .md path opens it via
markdown-command(defaultglow -p) - Agent glow: a pulsing inner border while an agent drives the pane over
the control socket (disable with
agent-glow = false) - Session notch placement:
notch-display = builtin | external | primary | all
- Settings window (⌘,): edits the config file, so changes apply live to every pane and persist
- Window chrome: standard macOS titlebar and tabs; optional traffic lights in
circle | square | rectangle | diamond - Transparency:
background-opacity = 0.9,background-blur = true(frosted behind-window blur) - Built-in AI pet: Infinitty's
>_<face ships enabled by default and changes expression as the terminal works, waits, succeeds, or fails. Setpet = noneto hide it, orpet = r2d2to use an installed~/.codex/petsspritesheet instead - Session notch:
notch = trueshows Claude Code to the left and the configured Infinitty pet to the right of a centered notch. Notchless external screens receive the same simulated center geometry. Click the indicator for recent sessions grouped with their subagents, prompts, runtime model names, and live/idle/done state; click a session to jump to its terminal or resume it, Option-click to continue it in the built-in Chat pane, or right-click for all recovery choices. Infinitty's running command, exit status (OSC 133), andinfinitty_activitysocket/MCP messages continue to appear in the same indicator.
Files, Chat, and Browser are first-class leaves in the same split tree as
terminals. Durable Channels connect those leaves without adding another panel.
The legacy toggle-sidebar socket command toggles the Files pane for compatibility.
- Files / Changes: One pane with a compact internal switch. Files provides directory search, syntax-highlighted previews, and breadcrumb navigation; Changes provides Git status, diffs, staging, and unstaging.
- Chat: Workspace-aware coding agent by default. It can inspect and edit the
selected checkout with provider-native coding tools, but cannot inspect or
control a terminal unless the explicit Chat/Terminal control is enabled; the
control stays in Chat mode when no terminal is attached. Terminal mode fails
closed for providers whose visible-terminal integration cannot be enforced.
Add multiple configured agents to one thread with
/add-agent claude,/add-agent codex, an exact provider/model alias, or the compact+beside the model picker. Agents receive short automatic names from their models (@sonnet,@opus,@sol) and subtle stable transcript colours. Ordinary prompts fan out to the ordered roster sequentially;@aliastargets selected members. Agents can also@mentionone another: a mention triggers a sequential peer response, bounded to one follow-up per agent per user turn so collaboration cannot recurse indefinitely. Replies share one transcript, show their author, and receive bounded explicitly-untrusted peer context. - Channel connections: drag one pane connector onto another to share room context, agent handoffs, Browser identity/control, and Files path-sync behavior. Click the badge for a compact membership summary. Room, plan, responsibility, role, and audit data remain available through MCP/headless control without a dedicated visual pane.
Each main tab owns its Files/Chat/Browser panes and nested layout independently. Channel membership stays bound to the connected pane endpoints.
For crash investigation, each run writes a synchronous structural ledger to
~/Library/Logs/Infinitty/pane-ledger/. Lines use a stable tab-<n> main-tab
ID and a literal + / - history for terminal, Files, and Chat leaves, along
with the split source, axis, and resulting pane tree. For example:
uptime=42.381 seq=18 tab=tab-2 + pane=chat reason=split-insert origin=split-chooser panes=[terminal:4,chat] tree=V[terminal:4,chat]
Changes is recorded as a page transition inside the Files pane, rather than
as a separate leaf. The ledger intentionally excludes terminal output, shell
commands, chat text, current directories, and file paths.
Full xterm-256color core: CSI cursor/erase/insert/delete/scroll-region ops, SGR incl. bold/faint/italic/underline/inverse/strikethrough, 16/256/truecolor, alt screen (vim, htop, tmux), origin mode, DEC line-drawing charset, tab stops, bracketed paste, DECALN, DA/DSR reports, wide (CJK) characters, OSC 0/2 titles, OSC 133 semantic prompts.
- No IME/dead-key composition, no ligatures
- Emoji render monochrome (alpha atlas; color atlas is a planned second texture)
- Combining characters are dropped rather than composed
- URLs that wrap across lines aren't detected as one link
- Titlebar/chrome changes apply to new windows, not already-open ones
swift testcovers the terminal engine; UI interactions are manual
Sources/CPty/ C shim: forkpty + TIOCSWINSZ (zero Swift/C friction)
Sources/InfinittyKit/
Terminal.swift cell grid, scrollback ring, VT parser, OSC 133 markers
Theme.swift 16-byte Cell, color encoding, 256-color palette
GlyphAtlas.swift CoreText -> shelf-packed R8 Metal atlas
Renderer.swift instanced Metal pipelines, display-link render thread
Shaders.swift MSL source (compiled at startup)
PTY.swift read thread, serial write queue
ControlServer.swift $INFINITTY_SOCKET agent interface (one per pane)
TerminalView.swift keyboard/mouse encoding, scrollback, live-resize
Session.swift one pane = terminal + pty + renderer + view + socket
Config.swift INFINITTY_* environment configuration
App.swift, main.swift windows, native tabs, split panes, menu
Sources/infinitty-agent/ provider-neutral CLI wrapper/context edge
Sources/infinitty-mcp/ MCP server and terminal/Channel tools
shell-integration/ OSC 133 zsh integration plus agent hooks/wrappers
MIT © Jason Kneen · infinitty.ai