Terminal Graph
Contents

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

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

Scroll or two-finger swipe to pan the canvas, and pinch or -scroll to zoom. Scrolling over an unfocused node pans the canvas — click the node to focus it first if you want to scroll its own content. Press ⌘⌥0 to fit everything on screen, ⌘⌥F to fit the selected node, and ⌘⌥M to toggle the minimap for jumping around large boards.

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

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.

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.

Press ⌘⌥L 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, but waypoint interactions require it.

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 tree spans every open project (see Projects) and is populated once at least one project folder is open.

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

All shortcuts are also listed next to their corresponding menu items inside the app.

ShortcutActionCategory
⌘,SettingsApp
⌘QQuit ApplicationApp
⌘OOpen FolderFile
⌘CCopyEdit
⌘VPasteEdit
⌘ASelect AllEdit
EscDeselect AllEdit
⌘NNew TerminalNodes
⌘⇧NNew NoteNodes
⌘⇧BNew BrowserNodes
⌘⇧ENew File EditorNodes
⌘DDuplicate SelectionNodes
⌘⇧DDuplicate Selection VerticallyNodes
⌘GNew Freeform GroupNodes
⌘⇧GNew Split GroupNodes
⌘WClose NodeNodes
⌘⇧TReopen Last ClosedNodes
⌘KCommand PalettePalette
⌘⌥LToggle Wiring ModeView
⌘RReload PageView
⌘⇧RReload Page from OriginView
⌘⏎Enter / Exit Focus ViewView
⌘⇧⏎Push / Pop Nested FocusView
⌘⌥⏎Toggle Group / Child FocusView
⌘⌥0Zoom to Fit AllView
⌘⌥FZoom to Fit NodeView
⌘⌥TTidy SelectionView
⌘⌥SToggle SidebarView
⌘⌥MToggle MinimapView
⌘=Zoom In / Increase Font SizeView
⌘−Zoom Out / Decrease Font SizeView
⌘0Reset Zoom / Font SizeView
⌘⌥←Navigate LeftNavigation
⌘⌥→Navigate RightNavigation
⌘⌥↑Navigate UpNavigation
⌘⌥↓Navigate DownNavigation
⌘MMinimize WindowWindow

Notes: Esc clears multi-selection when two or more nodes are selected. ⌘= / ⌘− / ⌘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. Focus 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. 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. 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.

PortTypeDirectionDescription
stdoutstreamoutputTerminal stdout
stderrstreamoutputTerminal stderr
stdinstreaminputInject into process stdin
textsignalinputInject text into the Ghostty surface and submit with Return, bypassing the stdin FIFO
exitsignaloutputProcess exit event; payload is the exit code
cwdstateoutputCurrent 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.

PortTypeDirectionDescription
urlstateoutputCurrent page URL
consolestreamoutputJavaScript console logs — not yet functional
domstateoutputDOM serialized as HTML, evaluated only when wired
navigatesignalinputLoad a URL string
reloadsignalinputReload the current page

Note

A free-form Markdown editor with optional file-backing, powered by Monaco.

PortTypeDirectionDescription
content-changedstateoutputCurrent note text, emitted on every edit
writesignalinputReplace content with incoming text
appendsignalinputAppend text, preserving cursor position and undo history
save-contentsignaloutputEmitted on save; payload is the note content
save-pathsignaloutputEmitted 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.

PortTypeDirectionDescription
pathstateoutputAbsolute path of the open file
contentstateoutputCurrent file content
savesignaloutputSave attempt; optional payload is the file path
writesignalinputReplace content
appendsignalinputAppend to content
opensignalinputLoad a new file by path

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).

PortTypeDirectionDescription
pathsignalinputLoad image from a file path
datasignalinputLoad image from raw bytes
refreshsignalinputReload 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.

PortTypeDirectionDescription
pathsignalinputLoad a video from a file path
refreshsignalinputReload from the current file

File Watcher

Watches files matching a glob pattern and emits a signal when one changes. Relative patterns resolve against the workspace root. Exact file paths are watched directly; glob patterns scan the relevant directory and match absolute, directory-relative, and workspace-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.

PortTypeDirectionDescription
changedsignaloutputFires 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.

PortTypeDirectionDescription
triggersignaloutputManual 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 workspace 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).

PortTypeDirectionDescription
inputsignalinputCommand invocation payload, written to stdin
lateststateoutputMost recent stdout
outputsignaloutputStdout from each completed command
errorsignaloutputStderr from each completed command
exitsignaloutputProcess 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.

PortTypeDirectionDescription
inputstreaminputContinuous bytes to frame
resetsignalinputClear the buffer and counters
lateststateoutputMost recent framed message
outputsignaloutputFramed message payload
countstateoutputNumber of messages emitted

Gate

Passes or blocks signal payloads based on open/closed state. Toggle it manually or drive it from other signals.

PortTypeDirectionDescription
inputsignalinputPayload to pass when open
opensignalinputSet the gate open
closesignalinputSet the gate closed
togglesignalinputFlip open/closed state
outputsignaloutputPassed payloads

Switch

Routes signal payloads by ordered regular-expression rules. The first matching rule wins. Payloads that match no rule go to default.

PortTypeDirectionDescription
inputsignalinputPayload to route
output-1signaloutputRule 1 matches
output-2signaloutputRule 2 matches
output-3signaloutputRule 3 matches
defaultsignaloutputNo 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.

PortTypeDirectionDescription
inputsignalinputPayload to delay, debounce, or throttle
outputsignaloutputForwarded 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.

PortTypeDirectionDescription
triggersignalinputRender the template
{{name}}stateinputDynamic input port for each placeholder
outputsignaloutputRendered 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.

PortTypeDirectionDescription
outputsignaloutputRequest 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 creates an empty freeform group at the viewport center.
  • ⌘⇧G creates an empty split-tree group at the viewport center.
  • 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.

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.

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

  1. Select one or more nodes (click, then -click to add to the selection). A whole group can be selected as a single unit.
  2. Open the command palette (⌘K) and run Create Blueprint from Selection…, or right-click empty canvas and choose the same action.
  3. 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.

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 .blueprint file, or drag it straight out to Finder.
  • Import from a file — double-click a .blueprint file in Finder to open it in Terminal Graph.
  • Import from a URL — run File → Import Blueprint from URL… and paste a link to a .blueprint file, 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 groups remember they were worktree-bound. When you insert the blueprint, Terminal Graph prompts you to bind it to a new worktree or insert without one.

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

TypeDescription
streamContinuous 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.
statePersistent 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).
signalDiscrete 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 → InputAllowed?
stream → streamYes
signal → stream, signal, stateYes
state → stream, signal, stateYes (replays current value on connect)
stream → signal or stateNo — 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

FlowUse it for
Trigger → RunRun a command manually or on an interval.
File Watcher → RunRun a command when matching files change.
Terminal stdout → Collect → RunInvoke a command once per framed stream message.
Webhook → Switch → RunRoute local HTTP requests to different command handlers.
Template → RunRender 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:

VariableDescription
$TG_STDINFIFO path for data flowing into this terminal from the graph
$TG_STDOUTFIFO path for data flowing out to whatever is wired to the stdout port
$TG_STDERRFIFO path for data flowing out to whatever is wired to the stderr port
$TG_NODE_IDThis node’s unique identifier
$TG_WRITE_TIMEOUT_MSTimeout 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

HookWhen it fires
post-worktree-createAfter 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.

CLI reference

The terminalgraph / tg CLI is automatically injected into terminal-node shells. No PATH setup is required.

Subcommands

CommandDescription
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 recvDrain $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 portsList the FIFO paths for all ports on the current node.
tg envPrint all TG_* environment variables.
tg help [--agent|--human]Show usage. Auto-detects agent mode via CLAUDECODE, CURSOR_AGENT, or CI=true.
tg versionPrint the app version.

Flags: tg run

FlagDescription
-i, --inRead $TG_STDIN only; command output goes to terminal.
-o, --outWrite to $TG_STDOUT only; read from terminal stdin.
-e, --capture-errSplit stderr to $TG_STDERR (default: merge into stdout).
-b N, --batch NRe-invoke the command per N input lines.
-t D, --batch-time DRe-invoke when the current batch is D old. Accepts 500ms, 2s, 1m, 1h, or bare seconds.

Flags: tg send

FlagDescription
-e, --errWrite to $TG_STDERR instead of stdout.
-n, --no-newlineSuppress the trailing newline.

Examples

001# Filter inbound stream and push matches onward
002tg run grep ERROR
003
004# One-off message to the graph
005tg send "deployment complete"
006
007# Notify when a long task finishes
008tg notify --title "Deploy" "production is live"
009
010# Generate output from a command that ignores stdin
011tg run --out date
012
013# Watch what upstream is pushing (debug)
014tg recv
015
016# Batch an infinite stream for a turn-based tool
017tg 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 via the command palette. System mode follows your macOS appearance and automatically switches between your chosen light and dark themes.

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.

MCP server

Terminal Graph includes a localhost MCP server that lets AI agents control the canvas programmatically. Enable it in Settings → MCP Server.

Capabilities

25 tools covering node lifecycle (create, move, resize, duplicate, focus, delete), port wiring (connect, disconnect), group management (create, add/remove members, delete, layout), blueprints (capture, instantiate, delete), reads (context, nodes, connections, blueprints, workspaces), canvas screenshots, terminal execution, and composite workflows.

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.

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

  • Default: off. Enable in Settings → MCP Server.
  • Port: 4930 (configurable, must be 1024–65535).
  • The settings tab shows copyable host config snippets for Claude Desktop, Claude Code, and Codex.

Settings

Press ⌘, to open the settings window.

General

  • 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.
  • 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 (%s is replaced with your query).

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/disable toggle, port configuration, and copyable host config 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 under blueprints/, and custom themes under themes/.
  • 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 under blueprints/, hook scripts under hooks/, and git worktrees.
  • Workspace files.terminalgraph-workspace files 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 to global.terminalgraph-workspace, and workspaces you save live under workspaces/, both inside ~/.config/terminalgraph/. A multi-project canvas lives in its workspace file, not in the per-project state.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.