Terminal Graph documentation
Introduction
Terminal Graph is a native macOS app from INTDEV — an infinite canvas where nodes are real terminals, browsers, notes, editors, file watchers, and small utility nodes.
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.
Auto-updates
The app is signed with an Apple Developer ID and notarized by Apple, and Sparkle keeps it up to date automatically. Updates install in place, and the macOS permissions you grant (folder access, microphone, notifications) carry across updates.
Getting started
Welcome Tour
The fastest way in is the guided tour, which opens on first launch and is always available from Help → Welcome Tour. It runs in its own managed project, so nothing you do in it touches your real work. Twelve core lessons cover the canvas, nodes, focus, the sidebar, the minimap, and groups; optional tracks cover startup commands, blueprints, coding agents, and git worktrees.
Each lesson gives you the concept, the keyboard and pointer routes to reach it, and one thing to try — and ticks off in the checklist beside the guide when you do it. Nothing is ever gated: jump to any lesson from All Lessons, or leave with Skip Tour at any point. Help → Reset Welcome Tour… starts it over.
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. Double-click a node’s title bar to fit it to its content: browser, note, editor, image, and video nodes snap to their natural size (clamped to the visible area, with images and video keeping their aspect ratio). Double-click again to restore the previous size.
Getting around
By default, scroll or two-finger swipe pans the canvas, while pinch or ⌘-scroll zooms. Scrolling over an unfocused node pans the canvas — click the node to focus it first if you want to scroll its own content. Change what a plain scroll does under Settings → General → Scrolling. Press ⌘⌥0 to fit everything on screen, and again to frame back where you were; ⌘⌥F fits the selected node, and ⌘⌥M toggles the minimap for jumping around large boards. Double-clicking empty canvas opens the node menu at that point.
Drag and drop
Drop a file, image, or video onto empty canvas to create a node for it: images become image nodes and videos open in a video player. Drop an image onto a terminal instead and its path is typed into the shell, which is handy for handing a screenshot to a CLI agent like Claude Code. Images dragged from a browser, Slack, or the macOS screenshot thumbnail work even when there’s no file on disk; they are saved to a temporary file first. A highlighted border shows which node a drop will land in, versus spawning a new node on the canvas.
Wiring nodes
Press ⌘⌥L to enter Wiring Mode. Ports and their hit areas appear only in this mode, so dragging a node edge outside it always resizes the node. Output ports sit on the right edge and input ports on the left; drag from one port to another to connect them, or right-click a port to disconnect it.
Connections render as smooth Bezier curves. In Wiring Mode you can add waypoints to route a curve around other nodes: double-click a wire to add a waypoint, then drag it into position. Double-click a waypoint to remove it. Right-click a wire or waypoint for more options.
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. Its Files, Nodes, and Blueprints tabs cover every project in the workspace. Press ⌘⇧F to search the active tab; use the arrow keys to select a result, Return to open it, Tab to switch tabs, and Escape to clear the search or return focus to the canvas. The command palette also has Search Files, Search Nodes, and Search Blueprints actions that open the corresponding tab and focus its search field.
The Files tab searches project roots and submodules. Use its header controls to create files and folders or show hidden and Git-ignored files. Project-root .ignore files are honored too. Right-click an item to rename, duplicate, reveal it in Finder, or move it to the Trash; drag files between folders or onto the canvas. In a workspace without a project, the tab offers Add Project… instead of disappearing.
Projects
A project is a folder on disk; a workspace is the window and canvas that holds it. A workspace can hold more than one project at once, so you can keep several codebases side by side on one canvas and wire dataflow between them.
Each project occupies its own tinted region on the canvas, and every node carries a small badge for the project it belongs to. The sidebar file tree spans all open projects.
Adding a project
- File → Add Project to Workspace…
- Right-click the canvas and choose Add Project…
- Drag a folder onto the canvas.
Opening a single folder still works as before; the window becomes a multi-project workspace the moment you add a second one. Use File → Save Workspace As… to save a multi-project layout you want to reopen later.
Keyboard shortcuts
These are the default shortcuts. Change them in Settings → Keyboard, where you can search every command, double-click a row to record new keys, clear or reset individual bindings, and restore all defaults. Menu items and the command palette update to show your custom bindings.
| Shortcut | Action | Category |
|---|---|---|
| ⌘, | Settings | App |
| ⌘Q | Quit Application | App |
| ⌘O | Open Folder | 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 |
| ⌘⇧D | Duplicate Node Vertically | Nodes |
| ⌘G | New Split Group / Ungroup | View |
| ⌘⇧G | New Freeform Group / Ungroup | View |
| ⌘W | Close Node | Nodes |
| ⌘⇧T | Reopen Last Closed | Nodes |
| ⌘Z | Undo | Edit |
| ⌘⇧Z | Redo | Edit |
| ⌘K | Command Palette | Palette |
| ⌘⇧F | Search Active Sidebar Tab | View |
| ⌘⌥L | Toggle Wiring Mode | View |
| ⌘R | Reload Page | View |
| ⌘⇧R | Reload Page from Origin | View |
| ⌘⏎ | Enter / Exit Isolation View | View |
| ⌘⇧⏎ | Push / Pop Isolation Stack | View |
| ⌘⌥⏎ | Toggle Group / Child Focus | View |
| ⌘⌥0 | Zoom to Fit All | View |
| ⌘⌥F | Zoom to Fit Node | View |
| ⌘⌥S | Toggle Sidebar | View |
| ⌘⌥M | Toggle Minimap | View |
| ⌘= | Zoom In / Increase Font Size | View |
| ⌘− | Zoom Out / Decrease Font Size | View |
| ⌘0 | Reset Zoom / Font Size | View |
| ⌘⌥← | Navigate Left | Navigation |
| ⌘⌥→ | Navigate Right | Navigation |
| ⌘⌥↑ | Navigate Up | Navigation |
| ⌘⌥↓ | Navigate Down | Navigation |
| ⌘M | Minimize Window | Window |
Notes: Shortcuts are context-aware, so the same keys can do different things depending on whether the app, workspace, canvas, or a particular node type has focus. Conflicting assignments are called out before they replace anything, and terminal conflicts let you choose whether Terminal Graph or the focused terminal gets the keys. Esc clears the current node selection. ⌘= / ⌘− / ⌘0zoom the canvas when no node is focused; with a note, editor, or browser focused they adjust that node’s font size instead, and terminal nodes handle font size directly via Ghostty. Isolation View presents a single node or group on its own; inside it, ⌘⇧⏎ steps in and out of a nested group and ⌘⌥⏎ switches focus between a group and its contents. ⌘G / ⌘⇧G ungroup when a group is selected, and do nothing when the selection is empty. ⌘Z undoes canvas edits — moves, resizes, duplication, grouping, closing, and split-pane layout — but only while the canvas has focus; a focused note, editor, or terminal takes ⌘Z for its own text.
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.
Local file links
Command-click a local path in a Terminal, Note, or Editor node to open it on the canvas. Relative terminal paths resolve from that terminal’s current directory; links in file-backed Notes and Editors resolve beside their source file. Add :line or :line:column to jump to a source location. Terminal Graph focuses an existing node when the file is already open, and directory links open a terminal there. Use Command-Shift-click to open the target with macOS instead.
Terminal
An interactive shell backed by libghostty with full stdio streams and process lifecycle signals. Right-click on terminal content for a context menu with Copy, Paste, Clear, Reset Terminal, Close Node, and group/split actions. The menu respects mouse capture, so it won’t interfere with vim or tmux. macOS input methods work with composed and marked text, including candidate windows positioned correctly while the canvas is zoomed.
| Port | Type | Direction | Description |
|---|---|---|---|
stdout | stream | output | Terminal stdout |
stderr | stream | output | Terminal stderr |
stdin | stream | input | Inject into process stdin |
text | signal | input | Inject text into the Ghostty surface and submit with Return, 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. Reload with ⌘R, or hard-reload with ⌘⇧R (also ⇧-click the reload button, or right-click the page and choose Hard Reload) to bypass the cache and re-fetch CSS and JS, which helps when a local dev server keeps serving stale assets. To make a node skip the cache on every reload, right-click the page and turn on Disable Cache. Type a search term rather than a URL into the address bar and it runs a web search; choose the engine in Settings → General → Browser.
| 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 | signal | input | Load a URL string |
reload | signal | input | Reload the current page |
Note
A live-preview Markdown editor with optional file backing, powered by CodeMirror. Headings, inline styles, code blocks, lists, checkboxes, block quotes, links, images, and tables render in place while their Markdown source remains directly editable.
| Port | Type | Direction | Description |
|---|---|---|---|
content-changed | state | output | Current note text, emitted on every edit |
write | signal | input | Replace content with incoming text |
append | signal | input | Append text, preserving cursor position and undo history |
save-content | signal | output | Emitted on save; payload is the note content |
save-path | signal | output | Emitted on save; payload is the file path (empty for ephemeral notes) |
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 | signal | input | Replace content |
append | signal | input | Append to content |
open | signal | input | Load a new file by path |
File-backed node behavior
Editor, Note, Image, Video, and File Watcher nodes follow files and containing folders renamed or moved in Finder or the Files sidebar. Trashed or missing targets are marked Missing and are not silently recreated. A clean file-backed Note reloads external changes automatically; if it also has unsaved edits, Terminal Graph asks whether to overwrite, reload, or cancel.
Image
Displays images from files or raw binary data. File-backed images auto-reload on change. No output ports. You can also create one by dragging an image onto the canvas (see Drag and drop).
| Port | Type | Direction | Description |
|---|---|---|---|
path | signal | input | Load image from a file path |
data | signal | input | Load image from raw bytes |
refresh | signal | input | Reload from the current file |
Video
Plays video files natively via AVKit, with standard playback controls. Like image nodes, it plays any format your Mac supports (MP4, MOV, and more). No output ports. Create one by dragging a video file onto the canvas (see Drag and drop), or from the right-click menu.
| Port | Type | Direction | Description |
|---|---|---|---|
path | signal | input | Load a video from a file path |
refresh | signal | input | Reload from the current file |
File Watcher
Watches files matching a glob pattern and emits a signal when one changes. Relative patterns resolve against the default project root. Exact file paths are watched directly; glob patterns scan the relevant directory and match absolute, directory-relative, and project-root-relative paths. Changes are debounced approximately 0.5 seconds to avoid event stampedes. By default, one signal is emitted per debounce window with the first matching file path. Enable Report all matches to emit one signal per matching file.
| Port | Type | Direction | Description |
|---|---|---|---|
changed | signal | output | Fires when a matching file changes; payload is the full file path |
Trigger
Emits a payload-free signal manually or on an interval. Use it to kick off scheduled flows, such as Trigger → Run for a periodic command.
| Port | Type | Direction | Description |
|---|---|---|---|
trigger | signal | output | Manual or interval trigger event |
Run
Runs a non-interactive shell command once for each input signal. The signal payload is written to the command’s stdin. Completed stdout is emitted as both output and latest; stderr and exit status have separate signal ports. Inputs queue while a command is running.
Run defaults to the default project root as its working directory. A custom working directory can be absolute or relative to that root. Built-in command templates include Custom Command (zsh), Filter Lines (grep), Query JSON (jq), Find & Replace (sed), Sort Lines (sort), and Word Count (wc).
| Port | Type | Direction | Description |
|---|---|---|---|
input | signal | input | Command invocation payload, written to stdin |
latest | state | output | Most recent stdout |
output | signal | output | Stdout from each completed command |
error | signal | output | Stderr from each completed command |
exit | signal | output | Process exit status |
Collect
Frames a continuous stream into discrete signal payloads. Use it between stream-producing nodes and signal-based utility nodes, such as Terminal stdout → Collect → Run.
Delimiters include newline, double newline, null byte, space, and a custom string. A timeout can flush the current buffer when no delimiter arrives.
| Port | Type | Direction | Description |
|---|---|---|---|
input | stream | input | Continuous bytes to frame |
reset | signal | input | Clear the buffer and counters |
latest | state | output | Most recent framed message |
output | signal | output | Framed message payload |
count | state | output | Number of messages emitted |
Gate
Passes or blocks signal payloads based on open/closed state. Toggle it manually or drive it from other signals.
| Port | Type | Direction | Description |
|---|---|---|---|
input | signal | input | Payload to pass when open |
open | signal | input | Set the gate open |
close | signal | input | Set the gate closed |
toggle | signal | input | Flip open/closed state |
output | signal | output | Passed payloads |
Switch
Routes signal payloads by ordered regular-expression rules. The first matching rule wins. Payloads that match no rule go to default.
| Port | Type | Direction | Description |
|---|---|---|---|
input | signal | input | Payload to route |
output-1 | signal | output | Rule 1 matches |
output-2 | signal | output | Rule 2 matches |
output-3 | signal | output | Rule 3 matches |
default | signal | output | No rule matched |
Delay
Changes when signal payloads are forwarded. Queue delays every payload independently. Debounce emits the latest payload only after quiet time. Throttle emits immediately, then keeps the latest trailing payload for the next window.
| Port | Type | Direction | Description |
|---|---|---|---|
input | signal | input | Payload to delay, debounce, or throttle |
output | signal | output | Forwarded payload |
Template
Renders text from retained placeholder values. Add placeholders like {{name}} to create matching state input ports. When the trigger port fires, Template renders the latest values and emits the result as a signal.
| Port | Type | Direction | Description |
|---|---|---|---|
trigger | signal | input | Render the template |
{{name}} | state | input | Dynamic input port for each placeholder |
output | signal | output | Rendered text |
Webhook
Turns localhost HTTP requests into signal payloads. Each node owns a path on the shared webhook server (bound to 127.0.0.1, port configurable in Settings) and emits the request body as a signal. Webhook is inbound-only; use Run with curl or another script for outbound HTTP.
By default the output payload is the raw request body text. Enable Include envelope to wrap the body in a JSON object with id, receivedAt, method, path, query, headers, and body. When the request has a JSON content type, the envelope’s body field contains the parsed JSON object (so downstream jq can access nested fields directly).
Enable Reject invalid JSON to return HTTP 400 for requests with a JSON content type whose body fails to parse. When off, invalid JSON is accepted and the raw text is used as the body.
| Port | Type | Direction | Description |
|---|---|---|---|
output | signal | output | Request body (or full envelope when enabled) |
Groups
A group is a container that bundles nodes together so you can move and resize them as a unit, optionally bound to an isolated git worktree. Groups have two layout strategies:
- Freeform — arrange member nodes anywhere inside the group’s interior. The group resizes to hold them.
- Split-tree — tile member nodes as panes, like a terminal multiplexer. Drag dividers to adjust ratios; right-click a pane to split it horizontally or vertically.
A group’s layout strategy is fixed at creation time. Groups cannot be nested.
Right-click anywhere on a group’s chrome for a single menu with its actions: add a node, split a pane, manage its worktree, Copy Group ID (for agent/MCP prompts), Rename (unbound groups), or delete it.
Creating a group
- ⌘G wraps the selected nodes in a split-tree group.
- ⌘⇧G wraps them in a freeform group instead.
- Both shortcuts need a selection — with nothing selected they do nothing, and their View menu items grey out. To create an empty group, right-click empty canvas and choose New Freeform Group or New Split Group to drop one where you clicked.
- The command palette (⌘K) exposes the same actions, plus New Freeform Group with Worktree and New Split Group with Worktree variants that create a group already bound to a fresh git worktree (see below).
Adding and removing members
- Right-click empty space inside a freeform group and choose Add node… to drop a new node directly into the group.
- Right-click any pane in a split-tree group and choose Split right… or Split below… to split the pane and add a new node alongside.
- Spawning a new terminal (⌘N) while a group is focused places the terminal inside the group automatically.
- Drag a member out past the dashed-red detach border and release to eject it back to the canvas. In split-tree groups, detaching a pane shows a ghost snapshot during the drag so you can see what you’re pulling out. Closing a node normally (⌘W) removes it from the group too.
- Drag an external node over a split-tree group to see a live green preview of where it will land. Drop to attach it at that position.
Ungrouping
Select a group and press ⌘G or ⌘⇧G again to dissolve it. Either key works — the layout only decides what gets created, and there is one way to take a group apart. Members stay exactly where they sit, and the View menu item retitles itself to Ungroup so the menu always names what the key will do. Ungrouping a worktree-bound group routes through the same confirmation as Detach Worktree…; cancelling that cancels the ungroup.
Ungrouping is not the same as deleting. Delete Group removes the member nodes with it and stays destructive.
Worktree binding
Bind a group to a git branch and Terminal Graph manages a dedicated git worktree for it. Every terminal spawned inside the group inherits the worktree path as its working directory, so the whole group operates against an isolated checkout of the branch.
- Bind an existing group: right-click the group’s title bar and choose Bind to Worktree…. Enter a worktree name (pre-filled from a themed name pack) and an optional branch name. The base branch defaults to
main. If the branch doesn’t exist yet, it’s created from the base. - Detached HEAD: leave the branch name empty in the bind dialog to create a worktree at a detached HEAD. The title bar shows the short SHA instead of a branch name.
- Detach without deleting: right-click the title bar and choose Detach Worktree…. The group and its members stay on the canvas; the worktree directory is removed. Optionally tick “Also delete branch” to run
git branch -D. - Delete the whole group: the standard delete flow on a worktree-bound group prompts you to confirm and optionally delete the branch as well.
- The group’s title bar shows the worktree name and branch (e.g. “europa · feat/auth”) while bound. If a branch is created or commits land on a detached HEAD, the title updates automatically.
- If the worktree directory disappears on disk, the group displays a banner with Recreate, Detach, and Delete actions so you can reconcile state without leaving the app.
MCP agents can create worktree-backed groups without opening a sheet. See MCP server for the worktree options.
Worktree groups require the workspace to be a git repository with at least one commit. Terminal Graph offers to create an initial commit if the repo has none yet.
Blueprints
A blueprint is a saved snapshot of a selection of nodes — or an entire group with its layout — that you can stamp out again later. Use them to capture frequently used setups: a debugger triple, a shell + editor + browser triad, a worktree group’s full layout.
Capturing a blueprint
- Select one or more nodes (click, then ⌘-click to add to the selection). A whole group can be selected as a single unit.
- Open the command palette (⌘K) and run Create Blueprint from Selection…, or right-click empty canvas and choose the same action.
- Give the blueprint a name and optional description, then choose a scope:
- Workspace — saved under
{PROJECT}/.terminalgraph/blueprints/; visible only inside this project. - Global — saved under
~/.config/terminalgraph/blueprints/; available in every workspace.
- Workspace — saved under
Inserting a blueprint
- Run Insert Blueprint… from the command palette to place at viewport center, or from the canvas right-click menu to place where you clicked.
- The picker lists every workspace and global blueprint. Type to filter by name; click to insert.
Sharing blueprints
Blueprints are portable: export one from your library to hand off to someone else, or import one they sent you.
- Export — in the sidebar Blueprints section, right-click a blueprint and choose Export… to save it as a
.blueprintfile, or drag it straight out to Finder. - Import from a file — double-click a
.blueprintfile in Finder to open it in Terminal Graph. - Import from a URL — run File → Import Blueprint from URL… and paste a link to a
.blueprintfile, such as a GitHub gist or raw URL.
Every import opens a preview first: the blueprint’s name, where it came from, a summary of its contents, and any terminal commands it would run. Nothing happens automatically; you choose a scope (This workspace or Global library) and Save to Library. Importing only adds the blueprint to your library; it never runs a command or drops nodes onto the canvas on its own. Insert it when you’re ready, like any other blueprint.
What gets captured
- Each selected node’s config and live runtime state (terminal CWD, browser URL, editor file path).
- Connections between captured nodes.
- Whole groups with their layout (freeform pane positions or split-tree structure) and all members.
- Worktree-bound groupsremember they were worktree-bound. Inserting from the app asks whether to create a new worktree or insert the group unbound. MCP’s
instantiate_blueprintrecreates each worktree by default; agents can skip recreation or override each group’s worktree name, branch, and base branch.
Blueprint files are plain JSON with a .blueprint extension, the same format your library stores and what you export to share, so a file copied straight from your library folder imports anywhere. Older <name>.blueprint.json files still load and migrate to .blueprint automatically.
Dataflow
Connections in Terminal Graph carry typed data between node ports. They render as smooth S-curves between output and input ports. Optionally, add waypoints in Wiring Mode to route curves around obstacles. The runtime validates type compatibility before a connection is established and rejects wiring between incompatible port types. Utility nodes use signal payloads by default; continuous streams stay streams until a node such as Collect frames them.
Port types
| Type | Description |
|---|---|
| stream | Continuous byte flow backed by named FIFO pipes. Used for terminal stdio and other long-running flows where chunk boundaries are not semantic. Stream ports connect to stream ports; use Collect to turn a stream into signal payloads. |
| 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, latest Run output). |
| signal | Discrete fire-and-forget events with optional string payloads. Used for process exit codes, save attempts, file changes, webhooks, and most utility-node routing. Not buffered. |
Port compatibility
| Output → Input | Allowed? |
|---|---|
| stream → stream | Yes |
| signal → stream, signal, state | Yes |
| state → stream, signal, state | Yes (replays current value on connect) |
| stream → signal or state | No — use Collect to frame a stream into signals |
Signal delivery order
When an output port fans out to multiple connections, subscribers fire synchronously in connection creation order. This is deterministic but invisible — there is no UI indicator for which connection was created first.
Practical consequence: if you wire a single Trigger to both a Gate’s toggle and input ports, the delivery order depends on which connection you drew first. If you need a guaranteed sequence (e.g., open the gate before sending data through it), insert a Delay node on the data path — even a minimal delay defers delivery to the next event-loop tick.
Utility-node patterns
| Flow | Use it for |
|---|---|
Trigger → Run | Run a command manually or on an interval. |
File Watcher → Run | Run a command when matching files change. |
Terminal stdout → Collect → Run | Invoke a command once per framed stream message. |
Webhook → Switch → Run | Route local HTTP requests to different command handlers. |
Template → Run | Render command input from retained state values. |
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.
Hooks
Hooks are executable scripts that run automatically in response to workspace events. Place them in .terminalgraph/hooks/ inside your project directory, named after the hook point they handle.
Available hooks
| Hook | When it fires |
|---|---|
post-worktree-create | After a worktree is created and attached to a group |
How hooks run
When a hook fires, Terminal Graph opens a small ephemeral terminal next to the group and runs the script inside it. The terminal border shows the hook’s state:
- Yellow — running
- Green — succeeded (auto-closes after a short delay)
- Red — failed (stays open so you can read the output)
Ephemeral hook terminals are excluded from state persistence and don’t appear in the recently-closed list.
Environment
Hook scripts receive environment variables describing the context that triggered them, such as TG_WORKTREE_PATH, TG_BRANCH_NAME, and TG_BASE_BRANCH_NAME.
Getting started
Terminal Graph creates sample templates in .terminalgraph/hooks/ when a workspace is opened. Copy and rename one to get started — they’re already marked executable.
Terminal Graph CLI reference
The terminalgraph / tg CLI 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 notify [-t TITLE] [message…] | Send a macOS notification from the terminal node, delivered by Terminal Graph. Requires notification permission in System Settings; otherwise it warns on stderr but still sends. |
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
001# Filter inbound stream and push matches onward002tg run grep ERROR003004# One-off message to the graph005tg send "deployment complete"006007# Notify when a long task finishes008tg notify --title "Deploy" "production is live"009010# Generate output from a command that ignores stdin011tg run --out date012013# Watch what upstream is pushing (debug)014tg recv015016# Batch an infinite stream for a turn-based tool017tg run --batch 50 --batch-time 10s claude -p "Summarize"
Run terminalgraph help for the complete reference.
Themes
Terminal Graph ships with 14 bundled themes and supports custom themes via TOML files. Themes control colors across the entire app — window chrome, canvas, nodes, title bars, sidebar, ports, and Monaco editors all follow the active theme.
Bundled themes
INTDEV Dark (default), INTDEV Light, Daybreak Dark, Daybreak Light, Nord, Nord Light, Catppuccin Mocha, Catppuccin Latte, Tokyo Night, Tokyo Night Day, Gruvbox Dark, Gruvbox Light, Girly Pop Dark, and Girly Pop Light.
Appearance mode
Choose between System, Light, or Dark in Settings → Appearance or with Switch Appearance… in the command palette. System mode follows your macOS appearance and automatically switches between your chosen light and dark themes. Use Switch Theme… for a searchable list of bundled and custom themes, with the active theme marked.
Custom themes
Create a .toml file in ~/.config/terminalgraph/themes/. Each theme defines color tokens for window, canvas, nodes, text, accent, sidebar, and ports. Use a bundled theme as a starting point — click Open Themes Folder in Settings to find the directory. Custom themes with the same name as a bundled theme override the built-in version.
Edits to theme colors in the Settings appearance tab persist immediately to the theme’s TOML file for custom themes. Edits to bundled themes are preview-only — save as a new theme to keep your changes.
Terminal Graph MCP server
Terminal Graph includes a localhost MCP server that lets AI agents control the canvas programmatically. Enable it in Settings → MCP Server.
Capabilities
Tools cover node lifecycle, port wiring, groups and worktrees, blueprints, projects, graph and workspace reads, canvas screenshots, terminal execution, browser automation, and composite workflows. The server advertises its current tool schemas to each MCP client, so agents can discover the full surface without relying on a fixed list in this manual.
Targeting windows
Most tools accept an optional workspace_id to target a specific open window. Omit it to hit the focused window. Run list_workspaces to enumerate every open window — each returns a stable id plus its project root path, and the focused window is flagged. A tool can target a window by either its id or its path, so agents can drive several windows in one session.
Referencing nodes and groups
MCP tools reference a node or group by its ID. To grab one, right-click a node and choose Copy Node ID, or right-click a group and choose Copy Group ID. The ID lands on the clipboard, ready to paste into an agent prompt. Selecting several nodes first copies all their IDs, newline-separated.
Worktree groups and blueprints
Pass a worktree object to create_group to create and bind a worktree without an interactive prompt. Its worktree_name, branch, and base_branch fields are optional. For captured worktree groups, instantiate_blueprint recreates worktrees by default. Use worktree_mode: "skip" to insert them unbound, or pass per-group overrides in worktrees;list_blueprints provides the group IDs those overrides use. Successful results include the resolved canvas group IDs, worktree paths, and branch or detached-HEAD details.
Screenshots
capture_canvas returns a PNG of the visible canvas by default. Pass an optional x, y, width, and heightbounding box (in canvas coordinates, the same space as node frames) to capture an explicit region — including off-screen areas — without panning. Regions are capped at 4000 pt per side.
Configuration
Agents can read a concise setup summary in llms.txt.
- Default: off. Enable in Settings → MCP Server.
- Connection:hosts launch Terminal Graph’s local stdio bridge, which connects to the app over a per-user Unix socket. There is no network port to configure.
- The settings pane shows server status and copyable host configuration for Claude Desktop, Cursor, Claude Code, and Codex.
Settings
Press ⌘, to open the settings window.
General
- Startup — choose whether the minimap and sidebar remember their last state, start open, or start closed. Remembered visibility is stored separately for each workspace.
- Worktree name pack — choose a themed name pack for worktree display names. Available packs: moons (default), islands, constellations, stations, ports, and signals.
- Grid spacing — controls snap-to-grid distance and grid dot density on the canvas.
- Scrolling — what an unmodified scroll does on the canvas. Pans the Canvas (the default for new installs) pans on every scroll; hold ⌘ and scroll to zoom. Zooms the Canvas zooms on every scroll; pan by holding Space and dragging or with the middle mouse button. Depends on the Device makes mouse wheels zoom while trackpads pan and pinch to zoom. Focused scrollable content keeps its own scroll in all three modes.
- Default search engine— when you type something that isn’t a URL into a browser node’s address bar, it runs a web search with this engine: Google (default), Bing, DuckDuckGo, or a custom URL template (
%sis replaced with your query).
Keyboard
Search every command and review its current key binding and activation context. Double-click a row to record a new shortcut, or right-click to set, clear, or reset it. Reset All restores every default. Terminal Graph warns before replacing a binding used in an overlapping context; when a focused terminal also claims the keys, you choose whether the app or terminal gets them.
Appearance
- Theme selection — pick from bundled or custom themes for both dark and light modes.
- Appearance mode — System, Light, or Dark.
- Token editing — adjust individual color tokens for window, canvas, nodes, text, sidebar, and ports.
- Canvas texture — set a backdrop for the canvas from the Canvas controls: None, a soft Vignette, or Top Lit.
- Fonts — choose the Terminal Font (from installed monospaced families) and the Interface Font (any installed family). Changes apply live to open terminals and the app chrome.
- Text size — scale Interface Text (menus, sidebar, and node chrome) and Node Content(what’s inside notes, editors, and other nodes) independently. Both apply live.
MCP Server
- Enable or disable the server, inspect its status and active sessions, restart it, and copy host configuration snippets. See MCP server for details.
Webhook
- Webhook port — the local port that webhook nodes listen on (default 4932). Change and click Restart to apply. All webhook nodes re-register automatically.
Account
Click the support button in the window chrome to sign in with your internet.dev account. You can also sign in or out via the command palette.
Supporters see a badge in the window chrome, which can be hidden in Settings. Terminal Graph works fully without an account — signing in is optional and only needed for support features.
Data storage
Terminal Graph persists data in three places:
- Global configuration —
~/.config/terminalgraph/. Holds app-level preferences, keybindings, global blueprints underblueprints/, and custom themes underthemes/. - Per-project data —
{PROJECT_DIR}/.terminalgraph/inside each project folder. Holds that project’s own canvas state (state.json, used when the folder is opened on its own), notes, editor buffers, project-scoped blueprints underblueprints/, hook scripts underhooks/, and git worktrees. - Workspace files —
.terminalgraph-workspacefiles capture a whole multi-project layout: the list of projects, their canvas regions, and every node and connection across them (see Projects). The home window persists toglobal.terminalgraph-workspace, and workspaces you save live underworkspaces/, both inside~/.config/terminalgraph/. A multi-project canvas lives in its workspace file, not in the per-projectstate.json.
Canvas state saves automatically as you work, so your layout survives crashes and force-quits, not just a clean quit. 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.
Stuck on something? Come talk to us.