Run your whole stack from one window. Native macOS, ~5 MB, no Electron.
Heart is a single-window launcher for every service your project needs — frontend, backend, workers, daemons. Start, stop, and watch them without opening a terminal or juggling tabs. Your project stays tidy, your focus stays on the code.
1. Install Heart — download
Heart.zip, unzip,
drag Heart.app into /Applications, then in Terminal:
xattr -cr /Applications/Heart.app && open -a Heart2. Generate heart.json for your project — cd into your project,
run claude, and paste:
Read https://raw.githubusercontent.com/ocracy/heart/refs/heads/main/heart-json-generator.md and generate
heart.jsonfor this project following that format.
Claude fetches the spec, walks your codebase, infers ports, and writes
a ready-to-import heart.json at the project root (with Claude
shortcuts for every directory that has a CLAUDE.md).
3. Import — drag the generated heart.json onto Heart's sidebar.
Every service lands under your project's folder, ready to start.
That's it. One window, your whole stack.
💡 Power-user shortcut. Install the spec as a Claude Code skill once, and skip the URL after that:
mkdir -p ~/.claude/skills/heart-json-generator && \ curl -fsSL https://raw.githubusercontent.com/ocracy/heart/main/heart-json-generator.md \ -o ~/.claude/skills/heart-json-generator/SKILL.mdThen in any project just say: "use the heart-json-generator skill".
Drop a heart.json onto the sidebar and Heart imports it as a folder. The
bundle's name becomes the folder name — no prompt, no setup. Drop a
second bundle, get a second folder. Each project stays scoped.
{
"name": "My Project",
"tasks": [
{
"id": "api",
"name": "API",
"command": "php artisan serve",
"cwd": "~/projects/my-project/api",
"port": 8000,
"url": "http://localhost:8000"
},
{
"id": "web",
"name": "Web",
"command": "npm run dev",
"cwd": "~/projects/my-project/web",
"folder": "Frontend",
"port": 5173,
"url": "http://localhost:5173"
}
]
}Nested folders via slash: "folder": "Backend/Workers" → My Project/Backend/Workers.
The legacy bare-array shape is still accepted (the file just lacks a
top-level name → Heart prompts for one).
💡 Generate this file automatically. Use the bundled
heart-json-generator.mdas a Claude Code skill — Claude scans your repo, detects every backend / frontend / queue service, infers ports, and writes a ready-to-importheart.json(with Claude shortcuts pre-wired for each package directory).
Tag a task with "kind": "claude" and Heart treats it as an agent shortcut,
not a service:
{
"id": "claude-web",
"name": "Claude (web)",
"command": "claude",
"cwd": "~/projects/my-project/web",
"kind": "claude"
}Click the shortcut and you get a multi-session detail pane:
- + opens another
claudesession in the same directory — fan out as many parallel agents as you want - Each session has its own terminal pill: status dot, name, pencil icon to rename ("Bug fix", "Feature spike", "Quick question"), X to kill just that session
- Sessions persist across sidebar selection. Open three Claudes, switch to your Laravel terminal, come back — all three are still there, exactly as you left them
- Shift+Enter inserts a newline in the prompt without submitting (no
claude /terminal-setupneeded — Heart handles it natively)
Set "url" on a task and you get a globe icon in the sidebar plus a
Browser tab in the detail pane:
- Address bar, back / forward, reload that actually works on flaky localhost servers
- 📱 Mobile toggle — clamps the viewport to 390 pt and swaps the user-agent so the page renders as iPhone Safari
- 🌐 Open in Chrome button — hands the current URL to the system Google Chrome (or the default browser if Chrome isn't installed)
- Cookies and localStorage persist across launches via the default WKWebView data store
Selected a task that's not running? Heart shows a big Activate button right where the terminal would go, with the command displayed underneath. Click it, the process starts, and the sidebar's play icon flips to stop in sync.
When a task crashes, the same overlay shows the exit code and a Restart button.
Powered by SwiftTerm
(xterm-256color compatible). Renders ANSI colors, cursor positioning,
mouse, scrollback, and TUIs — vim, htop, ngrok, k9s, claude,
fzf all work exactly as they would in iTerm2 or Apple Terminal. PTY
size follows the SwiftUI view (TIOCSWINSZ on resize), so full-screen
TUIs reflow correctly when you resize the window or collapse the sidebar.
Hit ⌃⌘S (or click the toolbar button) to hide the sidebar. The terminal
- browser take the whole window — distraction-free coding mode.
Cmd+Q runs every running task through a graceful chain in parallel:
- Ctrl+C through the PTY (SIGINT to the foreground process group)
killpg(SIGTERM)to the entire process group — covers shell children that wouldn't otherwise receive a signal when the parent zsh dies- Up to 2 s shared wait so all tasks drain in parallel
killpg(SIGKILL)for any stragglers
Result: the next launch finds 8000, 5173, 8082 all free. Force-quit is the only case Heart can't recover from (no handlers run on SIGKILL from the OS).
For when something else on your machine is still pinning :8000.
Click → lsof -ti tcp:8000 | xargs kill -9, scoped to the task's port.
Commands run under /bin/zsh -l -i -c — both .zprofile and .zshrc
sourced, so your custom PATH, aliases, fnm, mise, asdf, and rbenv hooks
all work without surprises.
Grab the latest Heart.zip:
- Unzip → drag
Heart.appinto/Applications. - In Terminal:
xattr -cr /Applications/Heart.app && open /Applications/Heart.app - Launch from Spotlight (
⌘+Space→ "heart") on subsequent runs.
Why step 2? Heart is ad-hoc signed. The
xattrcommand clearscom.apple.quarantine— safe and one-time only.
Don't want to use Terminal? Finder → right-click /Applications/Heart.app
→ Open → Open in the dialog.
git clone https://github.com/ocracy/heart.git
cd heart
./install.shInstalls to /Applications/Heart.app. Requires macOS 13+ and Swift 5.9
(Xcode Command Line Tools).
Heart starts with two placeholder tasks. Three ways to add your own:
A. Drag-and-drop — drop a heart.json (or any tasks.json) onto the
dashed area at the bottom of the sidebar. If the JSON has a top-level
name, the import is silent — your tasks land under that folder
immediately. Otherwise Heart prompts for a folder name.
B. Right-click → Edit — modify any task in a clean form. Saves to disk on every keystroke commit.
C. Settings → JSON editor (⌘+,) — paste raw JSON, validate, save.
A starter tasks.example.json ships with the repo.
Two accepted shapes:
Each task:
| Field | Type | Notes |
|---|---|---|
id |
string | Unique key (slug or UUID) |
name |
string | Sidebar label |
command |
string | Runs under /bin/zsh -l -i -c |
cwd |
string | Absolute path or ~/... (tilde is expanded) |
port |
int? | If set: enables readiness check + KILL PORT button |
url |
string? | Adds globe icon + in-app Browser tab |
folder |
string? | Sidebar grouping. Slash-separated for nesting: Backend/Workers |
kind |
string? | "claude" → multi-session shortcut, pinned with sparkles badge |
autoStart |
bool? | Reserved — saved but not yet acted on |
Persisted at ~/Library/Application Support/Heart/tasks.json.
1. Drop heart.json (your project's config) → all services + Claude shortcuts appear
2. Click each row's play icon → full stack spins up in seconds
3. Click the globe icon → see the live preview without leaving Heart
4. Click a Claude shortcut → terminal opens with `claude` in that dir
5. Need a parallel agent? + → second session, both alive, name them
6. Cmd+Q when done — every port is freed
That's the whole thing. One window. Everything together.
./build.sh # release build → ./Heart.app (no install)
./install.sh # build + install to /Applications/Heart.app
./dist.sh # universal (arm64 + x86_64) → Heart.zip for distributionTo regenerate just the icon:
swift scripts/make-icon.swift && iconutil -c icns AppIcon.iconset -o AppIcon.icnsrm -rf /Applications/Heart.app
rm -rf ~/Library/Application\ Support/Heart- Swift 5.9, SwiftUI, AppKit (macOS 13+)
- SwiftTerm — terminal emulation (xterm-256color, mouse, scrollback)
- WKWebView — in-app browser
- Foundation
Process+forkpty(via SwiftTerm) — child management - Swift Package Manager (no Xcode project)
- Sandbox disabled (required to spawn arbitrary child processes)
- ~5 MB binary
MIT — see LICENSE.

