Documentation
Introduction
Terminal Graph is a native macOS app from INTDEV — an infinite canvas where nodes are real terminals, browsers, notes, editors, and file watchers.
Drop them wherever, wire them up, and keep the whole mess of a project in one spatial view instead of cycling through tabs and tmux panes. The connections pipe data between nodes, which opens up workflows you can’t get from tabs alone. Curious to see what you come up with.
Download & install
Open the DMG, drag TerminalGraphBeta.app into your Applications folder, then eject the DMG.
Unsigned-app workaround
The Apple Developer account for Internet Development Studio Company is still being processed, so macOS will block the app on first launch with an “Apple could not verify this app is free of malware” dialog. Two ways past it:
- System Settings path: Attempt to open the app and let it fail. Then open System Settings → Privacy & Security, scroll to the bottom, and click Open Anyway. Confirm in the next dialog.
-
Terminal path: Run the following command, then
open the app normally.
xattr -dr com.apple.quarantine /Applications/TerminalGraphBeta.app
Auto-updates
Sparkle automatic updates work and the update archives are signed with a separate key that does not need Apple’s notarization blessing. You may see the quarantine dialog once per update until the Developer account is approved.
Getting started
Adding nodes
Right-click anywhere on the canvas to open the node menu. Select the type of node you want to add. Nodes can be repositioned by dragging their title bar and resized by dragging any corner or edge.
Wiring nodes
Drag from one port to another to create a connection. Ports live on the edges of each node; output ports are on the right side, input ports on the left. The port hitbox is a half-circle on the outside of the node edge — hover slowly near the edge to reveal it. Right-click a port to disconnect it.
Press ⌘⇧D to toggle Wiring Mode, which makes all ports visible simultaneously and is easier to use when wiring many connections at once. Wiring works without Wiring Mode too.
Command palette
Press ⌘K to open the command palette. It is the fastest way to reach any action — adding nodes, changing settings, running commands — without navigating menus.
Sidebar
Press ⌘⌥S to toggle the sidebar, which contains a file tree and workspace switcher. The file tree is only populated when the app is opened with a project folder.
Keyboard shortcuts
All shortcuts are also listed next to their corresponding menu items inside the app.
| Shortcut | Action | Category |
|---|---|---|
| ⌘Q | Quit Application | App |
| ⌘O | Open Folder | File |
| ⌘⇧O | Open Folder in New Window | File |
| ⌘C | Copy | Edit |
| ⌘V | Paste | Edit |
| ⌘A | Select All | Edit |
| Esc | Deselect All | Edit |
| ⌘N | New Terminal | Nodes |
| ⌘⇧N | New Note | Nodes |
| ⌘⇧B | New Browser | Nodes |
| ⌘⇧E | New File Editor | Nodes |
| ⌘D | Duplicate Node | Nodes |
| ⌘W | Close Node | Nodes |
| ⌘⇧T | Reopen Last Closed Node | Nodes |
| ⌘K | Command Palette | Palette |
| ⌘⇧D | Toggle Wiring Mode | View |
| ⌘⏎ | Toggle Focus Mode | View |
| ⌘⌥0 | Zoom to Fit All | View |
| ⌘⌥F | Zoom to Fit Node | View |
| ⌘⌥T | Tidy Selection | View |
| ⌘⌥S | Toggle Sidebar | View |
| ⌘⌥M | Toggle Minimap | View |
| ⌘= | Increase Font Size | View |
| ⌘− | Decrease Font Size | View |
| ⌘0 | Reset Font Size | View |
| ⌘⌥← | Navigate Left | Navigation |
| ⌘⌥→ | Navigate Right | Navigation |
| ⌘⌥↑ | Navigate Up | Navigation |
| ⌘⌥↓ | Navigate Down | Navigation |
| ⌘M | Minimize Window | Window |
Notes: Esc clears multi-selection when two or more nodes are selected. Font size shortcuts apply to adjustable nodes; terminal nodes handle font size directly via Ghostty. The navigation arrow shortcuts are canvas-level keyboard monitor shortcuts, not menu items.
Node types
Each node type exposes a set of typed ports. Connections are validated before wiring; incompatible port types are rejected. Port types are described in the Dataflow section.
Terminal
An interactive shell backed by libghostty with full stdio streams and process lifecycle signals.
| Port | Type | Direction | Description |
|---|---|---|---|
stdout |
stream | output | Terminal stdout |
stderr |
stream | output | Terminal stderr |
stdin |
stream | input | Inject into process stdin |
text |
stream | input | Inject raw bytes directly into the Ghostty surface, bypassing the stdin FIFO |
exit |
signal | output | Process exit event; payload is the exit code |
cwd |
state | output | Current working directory, tracked via OSC 7 |
Browser
An embedded web browser backed by WKWebView with
navigation, console logging, and DOM inspection.
| Port | Type | Direction | Description |
|---|---|---|---|
url |
state | output | Current page URL |
console |
stream | output | JavaScript console logs — not yet functional |
dom |
state | output | DOM serialized as HTML, evaluated only when wired |
navigate |
stream | input | Load a URL string |
reload |
signal | input | Reload the current page |
Note
A free-form Markdown editor with optional file-backing, powered by Monaco.
| Port | Type | Direction | Description |
|---|---|---|---|
content |
state | output | Current note text |
write |
stream | input | Replace content with incoming text |
append |
stream | input | Append text, preserving cursor position and undo history |
save |
signal | output | Emitted whenever a save is attempted |
Editor
A file-backed code editor powered by Monaco with language detection and external file watching. Auto-reloads on external change when not dirty.
| Port | Type | Direction | Description |
|---|---|---|---|
path |
state | output | Absolute path of the open file |
content |
state | output | Current file content |
save |
signal | output | Save attempt; optional payload is the file path |
write |
stream | input | Replace content |
append |
stream | input | Append to content |
open |
stream | input | Load a new file by path |
Image
Displays images from files or raw binary data. File-backed images auto-reload on change. No output ports.
| Port | Type | Direction | Description |
|---|---|---|---|
path |
stream | input | Load image from a file path |
data |
stream | input | Load image from raw bytes |
refresh |
signal | input | Reload from the current file |
File Watcher
Watches a directory for file changes matching a glob pattern. The pattern is fnmatch-style and is debounced approximately 0.5 seconds to avoid event stampedes.
| Port | Type | Direction | Description |
|---|---|---|---|
changed |
signal | output | Fires when a matching file changes; payload is the full file path |
path |
stream | input | Update the glob pattern |
Dataflow
Connections in Terminal Graph carry typed data between node ports.
The
DataflowRuntime validates type compatibility before a
connection is established and rejects wiring between incompatible
port types.
Port types
| Type | Description |
|---|---|
| stream | Continuous text flow backed by named FIFO pipes. Used for stdio, file content, and URL strings. Can be read and written many times. Equivalent to a Unix pipe. |
| state |
Persistent key/value backed by a StateStore.
Emits on change. New subscribers receive the current value
immediately (e.g., current URL, current file path).
|
| signal | Discrete fire-and-forget events with optional payloads. Used for process exit codes, save attempts, and file change notifications. Not buffered. |
Environment variables
Terminal nodes expose their streams through environment variables that Terminal Graph injects automatically into each shell session:
| Variable | Description |
|---|---|
$TG_STDIN |
FIFO path for data flowing into this terminal from the graph |
$TG_STDOUT |
FIFO path for data flowing out to whatever is wired to the stdout port |
$TG_STDERR |
FIFO path for data flowing out to whatever is wired to the stderr port |
$TG_NODE_ID |
This node’s unique identifier |
$TG_WRITE_TIMEOUT_MS |
Timeout for write retries (default 2000 ms) |
The FIFO files are backed by Unix named pipes, so cat,
tee, and shell redirection work on them like any normal
file. The tg CLI wraps these operations for common
patterns.
CLI reference
The terminalgraph / tg CLI (v0.2.0-beta)
is automatically injected into terminal-node shells. No PATH setup
is required.
Subcommands
| Command | Description |
|---|---|
tg run [flags] <cmd> [args…] |
Pipe data through a command. Default: bidirectional stdin/stdout; stderr merged. |
tg send [flags] [text…] |
Write text to $TG_STDOUT (or
$TG_STDERR with --err).
|
tg recv |
Drain $TG_STDIN to terminal stdout. |
tg ports |
List the FIFO paths for all ports on the current node. |
tg env |
Print all TG_* environment variables. |
tg help [--agent|--human] |
Show usage. Auto-detects agent mode via
CLAUDECODE, CURSOR_AGENT, or
CI=true.
|
tg version |
Print the app version. |
Flags: tg run
| Flag | Description |
|---|---|
-i, --in |
Read $TG_STDIN only; command output goes to
terminal.
|
-o, --out |
Write to $TG_STDOUT only; read from terminal
stdin.
|
-e, --capture-err |
Split stderr to $TG_STDERR (default: merge into
stdout).
|
-b N, --batch N |
Re-invoke the command per N input lines. |
-t D, --batch-time D |
Re-invoke when the current batch is D old. Accepts
500ms, 2s, 1m,
1h, or bare seconds.
|
Flags: tg send
| Flag | Description |
|---|---|
-e, --err |
Write to $TG_STDERR instead of stdout. |
-n, --no-newline |
Suppress the trailing newline. |
Examples
# Filter inbound stream and push matches onward
tg run grep ERROR
# One-off message to the graph
tg send "deployment complete"
# Generate output from a command that ignores stdin
tg run --out date
# Watch what upstream is pushing (debug)
tg recv
# Batch an infinite stream for a turn-based tool
tg run --batch 50 --batch-time 10s claude -p "Summarize"
Run terminalgraph help for the complete reference.
Data storage
Terminal Graph persists data in two locations:
-
Global configuration —
~/.config/terminalgraph/. Holds app-level preferences, keybindings, and canvas state for freestanding (no-workspace) windows. -
Per-workspace data —
{PROJECT_DIR}/.terminalgraph/inside each project directory opened as a workspace. Holds canvas state, node layouts, notes, and editor buffers scoped to that workspace.
Back up these directories if you care about preserving your layouts. Deleting them, or uninstalling the app, removes all locally persisted state.
Feedback
The app is still in beta, so expect things to break. There are two ways to report issues from inside the app:
- Help → Report a Bug… — for something that is broken.
- Help → Send Feedback… — for ideas, UX gripes, or anything else.
Both menu items open a pre-filled email in your default mail client, addressed to [email protected], with your app version, macOS version, and a few other details pre-populated. Remove anything you’re not comfortable sharing before sending.
You can also email [email protected] directly.
What makes a useful bug report
- What you did
- What you expected to happen
- What actually happened
- Screenshots are useful. A short screen recording is even better.
- A half-written “hey this felt weird” is better than nothing.
Notes
- No telemetry, no analytics, no crash reporters. If I hear about a bug, it’s because you told me.
-
Browser node limitations. The
consoleoutput port is not yet functional. All other browser node features, includingdom, work. Fix forconsoleis on the list. - Updates. Updates will land semi-regularly. Some will be rough. Sorry in advance.
- Sharing. You are free to share pictures, videos, and write about Terminal Graph anywhere. I'd appreciate it if you would tag me on socials (listed below) so I can see what you are making.
Sign-off
That’s it. Go break things.
From the bottom of my heart, thank you.
— Caidan
[email protected] · @caidanwilliams on X · caidan.dev on Bluesky