Shortcuts and Macros: Eliminating Window Switching Entirely
Callipso now mirrors your terminal shortcuts from the overlay and chains them into reusable macros on an N8N-style visual canvas — zero window switching required.
Shortcuts and Macros: Eliminating Window Switching Entirely
We shipped keyboard shortcut mirroring last week. Press Cmd+T on the overlay, a new tab opens in iTerm2. Press Cmd+D, a split appears in Ghostty. Seven terminal apps, one overlay surface. Your muscle memory works without switching windows.
That solved one problem: individual actions. But developers do not work in single actions. You create a tab, cd into a directory, launch Claude Code, send a prompt, wait, switch to another terminal, send a follow-up. That sequence — five to eight steps — is the real workflow. Repeating it manually ten times a day is where the friction lives.
So we built a macro system on top of the shortcut mirroring layer. An N8N-style visual canvas where you chain terminal actions into reusable sequences. Drag nodes, wire them together, pin the macro to a terminal footer button, and run it with one click.
Shortcut mirroring gives you control of any terminal from the overlay. Macros let you chain that control into workflows. Together, they eliminate window switching entirely.
The Macro Canvas
The canvas is a free-form 2D node editor — not a linear list. Nodes can be positioned anywhere on an infinite grid, connected by SVG Bezier arrows, and grouped with colored sticky notes. The design is deliberately inspired by N8N: if you have used a visual workflow builder, the interaction model is familiar.
Canvas Features:
├── Zoom: 0.25x to 2.0x with scroll wheel, adaptive grid background
├── Pan: Drag background or hold Space
├── Selection: Click (single), Shift+drag (rubber band), Cmd+A (all)
├── Positioning: Free-form drag, optional 20px grid snapping
├── Undo/Redo: 30-snapshot deep-copy stack
└── Annotations: Colored sticky notes (5 colors, resizable, draggable)
Each node represents one step. We support six step types, each targeting a different level of the system:
| Step Type | What It Does | Example |
|---|---|---|
text / send / submit | Type text into a terminal (with or without Enter) | cd ~/project && npm run dev |
shortcut | Execute a keyboard shortcut via the mirroring layer | Cmd+D (split pane in Ghostty) |
switch | Activate a specific terminal by UUID | Switch to "Terminal 3 (Cursor)" |
delay | Pause execution with a countdown toast | Wait 2 seconds for server startup |
callipso | Execute a built-in Callipso action | Create worktree, auto-commit, fork session |
macro | Run another macro (max depth 1) | Chain a "setup" macro into a "deploy" macro |
The callipso step type deserves a closer look. It exposes 15 built-in actions that go beyond what any single terminal shortcut can do:
Terminal Actions: create-claude, create-empty, create-worktree, stop-active, fork-session
Command Actions: send-default, send-clipboard
Git Actions: commit-push, commit-only, auto-commit
Claude Desktop: create-session, focus, stop, next-session, prev-session
A single macro can create a terminal in Cursor, cd into a worktree, launch Claude Code, send a prompt, wait for it to spin up, switch to another terminal, and send a follow-up there. All from one click.
How Shortcuts Feed Into Macros
The macro system reuses the shortcut mirroring infrastructure directly. When you add a shortcut step to a macro, the picker shows every shortcut from every supported app — the same maps that power live shortcut mirroring:
Shortcut Picker (collapsible per-app):
├── Active App (pinned to top)
│ ├── Cmd+T — New Tab
│ ├── Cmd+W — Close Tab
│ └── Cmd+D — Split Right
├── iTerm2 (31 shortcuts)
├── Ghostty (18 shortcuts)
├── Warp (18 shortcuts)
├── Kitty (19 shortcuts)
└── Cursor / VS Code / Windsurf (8 shortcuts)
The picker also supports live key recording — press a key combination and it captures the accelerator directly. This is useful for shortcuts not in the predefined maps.
When the macro executes a shortcut step, it calls the same execute-shortcut IPC channel that the live mirroring uses. The execution path is identical:
Macro Step → execute-shortcut IPC
→ KeybindingManager.executeShortcutByAccelerator()
→ resolve active app from store
→ route to per-app executor
→ iTerm2: AppleScript / Ghostty: TTY write / Warp: API / IDE: HTTP
This means macros inherit all the per-app adapter logic. A Cmd+D step does the right thing whether the target terminal is in iTerm2 (AppleScript split), Ghostty (TTY command), or Cursor (VS Code command via extension HTTP endpoint).
The Keyboard-First Canvas
The canvas has a full keyboard shortcut layer that mirrors the patterns developers already know:
Meta: Cmd+K (command palette), Cmd+S (save), Cmd+Enter (run)
Edit: Cmd+Z/Shift+Z (undo/redo), Cmd+A (select all), Cmd+D (duplicate)
Clip: Cmd+C/X/V (copy/cut/paste with offset)
View: +/- (zoom), 0 (reset), 1 (fit all), Space (pan mode)
Navigate: Arrow keys (move between nodes), Shift+Arrow (extend selection)
Nudge: Alt+Arrow (move nodes 1px or grid-snap distance)
Add: N or Tab (new step), Shift+S (sticky note)
Toggle: D (disable/enable selected nodes)
Cmd+K opens a command palette with fuzzy search across six categories: Add Step, View, Edit, Macro, and Canvas. Every action has its keyboard shortcut displayed inline.
The result is that power users never touch the mouse. Arrow to a node, press Enter to edit inline, Tab to add the next step, Cmd+Enter to run, Cmd+S to save.
Storage and Pinning
Macros are stored in localStorage under the callipso-macros key. Each macro has an ID, a name, an icon (from a set of 15), an array of steps with positions, and an optional array of sticky notes:
{
id: "macro_1741234567",
name: "Setup Dev Environment",
icon: "rocket",
steps: [
{ type: "callipso", action: "create-claude", _x: 40, _y: 60 },
{ type: "delay", ms: 2000, _x: 220, _y: 60 },
{ type: "submit", command: "fix the auth bug", _x: 400, _y: 60 }
],
stickyNotes: [
{ id: "note_1", text: "Wait for Claude to boot", x: 180, y: 120, color: "yellow" }
],
createdAt: 1741234567000,
lastRunAt: 1741234590000
}
Up to three macros can be pinned to terminal footer buttons. Pinned macros appear as small icon buttons next to each terminal in the list. Click to run. Right-click to open the editor with that macro selected. The pin system uses a separate localStorage key (callipso-macro-pins) so pinning state is independent of macro data.
The Monolith-to-Modules Refactor
The macro system started as a single 1,200-line JavaScript file. As features accumulated — canvas transforms, undo/redo, keyboard handlers, pickers, sticky notes — the file became unmaintainable.
We split it into 11 modules:
callipso/overlay-modules/terminal/macro/
├── macro-state.js 185 lines — shared mutable state, no dependencies
├── macro-icons.js 74 lines — SVG icon + emoji migration map
├── macro-storage.js 156 lines — CRUD + pin system (localStorage)
├── macro-execution.js 342 lines — run engine, step executor, toast system
├── macro-canvas.js 406 lines — transforms, zoom, pan, undo/redo, grid
├── macro-nodes.js 452 lines — node rendering, arrows, inline editing
├── macro-keyboard.js 375 lines — centralized keyboard handler
├── macro-sticky.js 232 lines — colored annotation blocks
├── macro-palette.js 254 lines — Cmd+K command palette with fuzzy search
├── macro-pickers.js 429 lines — shortcut/terminal/action/macro pickers
└── macro-command.js 780 lines — orchestrator, popover lifecycle, public API
Total: ~3,685 lines across 11 files. Each module exposes its API through lazy getters to resolve circular dependencies without import gymnastics. State is centralized in macro-state.js — one file owns all mutable state, preventing duplication.
The split was mechanical — no behavior changes, no new features. But it made the next wave of features (sticky notes, command palette, undo/redo) tractable. Each feature was a change to one or two modules instead of a change to a 1,200-line monolith.
What Nobody Else Built
We researched every tool in the space. The macro canvas occupies an empty intersection:
Workflow builders (N8N, Windmill, Node-RED) have visual canvases, but they target API integrations and cloud operations. None of them chain terminal commands to specific terminals by UUID.
Terminal multiplexers (Claude Squad, Superset, Agent Deck) manage multiple agent sessions, but they are keyboard-driven with no visual macro system. Creating a repeatable workflow means writing a shell script.
Automation tools (Keyboard Maestro, BetterTouchTool) can chain keystrokes, but they require manual per-app configuration. They cannot discover terminals, resolve UUIDs, or route through per-app adapters.
The macro canvas sits at the intersection: a visual workflow builder that speaks terminal-native operations through the adapter layer. It knows about Ghostty splits, iTerm2 sessions, Cursor terminals, and Claude Code lifecycle — because it builds on the same infrastructure that powers live shortcut mirroring.
What We Did Not Build
Conditional branching. Macros are linear sequences today. There is no "if Claude responds with an error, run step B instead of step C." We want to see how people use the linear model before adding complexity.
Cloud sync. Macros live in localStorage. If you use multiple machines, your macros do not follow you. A sync layer would need conflict resolution for macros edited on both machines — solvable, but not yet necessary.
Macro sharing. No export/import format yet. A shareable JSON schema would let teams distribute standard workflows. This is on the roadmap.
Step-level error handling. If a step fails (terminal not found, shortcut unsupported), the macro stops with a toast. There is no retry logic or fallback step. For most macros — five to eight steps — stopping on error and letting the user investigate is the right default.
What We Learned
-
Shortcut mirroring is infrastructure, not a feature. The feature is "control any terminal without switching windows." Shortcut mirroring is the plumbing. Macros are the interface that makes the plumbing useful for repeated workflows.
-
Node canvases need keyboard-first design. The mouse-first interaction model works for discovery, but developers live on the keyboard. If Cmd+K, arrow keys, and Tab do not work fluently, the canvas is a toy. We invested heavily in the keyboard layer and it shows in daily use.
-
Lazy getters solve circular dependencies in module splits. When 11 modules need to reference each other (the execution engine calls the node renderer for visual feedback, the keyboard handler calls the canvas for transforms), lazy getters (
Nodes = () => Callipso.macroCommand._m.nodes) break the cycles without barrel files or dependency injection. -
Undo as deep-copy snapshots is simple and correct. Delta-based undo is more space-efficient but harder to get right with free-form positioning, sticky notes, and step mutations happening simultaneously. A 30-entry stack of JSON deep copies uses negligible memory and is trivially correct.
-
Pin-to-footer closes the gap between building and using. A macro that lives only in the editor is a novelty. A macro that appears as a button on every terminal in the list is a tool. The pin system — three lines of localStorage code — is what makes macros practical.
Seven apps. Eleven modules. Six step types. One canvas. Your terminal workflows are now visual, repeatable, and one click away.