terhechte/Cormac

13

stars

186

commits

Swift

primary language

Jul 1, 2026

updated

README

Cormac

Cormac is a macOS AppKit terminal host that embeds libghostty via a locally built GhosttyKit.xcframework.

Cormac runs unsandboxed (it spawns user shells, like any terminal emulator) with the Hardened Runtime enabled. See SECURITY.md for the full security model, including how agent file access is confined to the workspace.

Assets/cormac-workspace-browser.png

Project Layout

  • Vendor/ghostty: source checkout of ghostty-org/ghostty
  • Vendor/ghostty/macos/GhosttyKit.xcframework: native macOS Ghostty binary built from that source
  • Cormac.xcodeproj: canonical development project for the macOS app, signing, and SwiftUI previews
  • Sources/Cormac: the minimal AppKit host application
  • Sources/CormacACP: Agent Client Protocol card support
  • Sources/CormacAgentHook: helper used by terminal-agent notification hooks
  • Scripts/build-ghosttykit.sh: helper to rebuild the local Ghostty xcframework
  • Scripts/build-mlx-metallib.sh: helper used by Xcode and the SwiftPM bundler to build mlx.metallib
  • Scripts/make-app-bundle.sh: packages the SwiftPM executable as Build/Cormac.app with Ghostty resources

Development Build Setup

Cormac is currently set up for Apple Silicon macOS development. The project targets macOS 14.0 and Package.swift uses Swift tools 6.2, so use an Xcode release that includes the Swift 6.2 toolchain.

Prerequisites

Install the external tools used by the checked-in project and build scripts:

brew install libgit2 zig@0.15
  • Xcode: install the full Xcode app, not only the Command Line Tools. Make sure xcode-select points at it:
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
  • Metal Toolchain: Ghostty and MLX both compile Metal sources. If xcrun -sdk macosx metal --version fails, install the component:
xcodebuild -downloadComponent MetalToolchain
  • libgit2: Package.swift and Cormac.xcodeproj link -lgit2 from /opt/homebrew/opt/libgit2/lib. If Homebrew is installed somewhere else, update the library search paths in both places.
  • Zig: Ghostty requires Zig 0.15.2 or newer. Scripts/build-ghosttykit.sh automatically prefers /opt/homebrew/opt/zig@0.15/bin/zig; set ZIG_BIN=/path/to/zig if you need a different install.

Rust is not needed for normal Cormac development. Cormac consumes VikerKit 0.1.5 or newer from https://github.com/terhechte/VikerKit.git as a Swift package dependency; VikerKit provides the release-hosted editor engine xcframework and the reusable editor autosuggestion UI.

First Checkout

Clone with submodules:

git clone --recurse-submodules <repo-url>
cd Cormac

If you already cloned without submodules or LFS, fix the checkout in place:

git submodule update --init --recursive

Vendor/ghostty is the only top-level git submodule. SwiftPM dependencies such as VikerKit, MLX, Swift ACP, Markdown UI, and Hugging Face Transformers are resolved later by Xcode or swift build.

Build GhosttyKit

Build the native Ghostty library before opening or building Cormac:

./Scripts/build-ghosttykit.sh

This runs Ghostty's Zig build with:

zig build -Demit-xcframework=true -Demit-macos-app=false -Dxcframework-target=native -Doptimize=ReleaseFast

The script also checks that Zig is new enough and that Xcode's Metal compiler is available. The important outputs for Cormac are:

  • Vendor/ghostty/macos/GhosttyKit.xcframework
  • Vendor/ghostty/zig-out/share/terminfo
  • Vendor/ghostty/zig-out/share/ghostty

Re-run this script when Vendor/ghostty changes or when you need to rebuild the embedded Ghostty binary. You do not need to run it for every Swift edit.

Xcode Build

The preferred development flow is Xcode:

open Cormac.xcodeproj

Use the Cormac scheme to build, run, archive, or open SwiftUI previews. Xcode resolves Swift package dependencies into DerivedData automatically on first build.

The Xcode app target has two important run script phases:

  • Embed CormacAgentHook helper: builds/copies the helper into Cormac.app/Contents/MacOS/.
  • Build and embed mlx.metallib: runs Scripts/build-mlx-metallib.sh and writes mlx.metallib into the app resources.

So for Xcode builds, you do not manually compile the MLX Metal library. Let the build phase do it. The first build after package resolution can take longer because it has to compile the MLX kernels; incremental builds skip the work when the existing mlx.metallib is newer than the kernel sources.

SwiftPM Build

SwiftPM is useful for command-line builds, tests, and local bundling:

./Scripts/build-ghosttykit.sh
swift build
swift test

Bare swift build compiles the Swift products, but it does not produce a complete .app bundle with Ghostty resources. To create a runnable development bundle, use:

./Scripts/make-app-bundle.sh
open Build/Cormac.app

make-app-bundle.sh builds Cormac, builds CormacAgentHook, invokes Scripts/build-mlx-metallib.sh, and copies Ghostty's terminfo and shell-integration resources into the app bundle.

Running the bundled app gives Ghostty access to its terminfo database and shell-integration resources. The package intentionally starts as a single-window, single-surface terminal so it stays small and easy to customize. The host runtime and surface view live in Sources/Cormac/GhosttyRuntime.swift and Sources/Cormac/GhosttySurfaceView.swift.

Troubleshooting Build Setup

  • ld: library 'git2' not found: install libgit2 with brew install libgit2, or update the project/package library search paths if your Homebrew prefix is not /opt/homebrew.
  • Ghostty checkout not found: run git submodule update --init --recursive.
  • zig ... is missing, broken, or older than 0.15.2: run brew install zig@0.15, or set ZIG_BIN to a Zig 0.15.2+ binary.
  • cannot execute tool 'metal' due to missing Metal Toolchain: run xcodebuild -downloadComponent MetalToolchain.
  • MLX Metal kernels not found: let Xcode resolve packages once, or run swift build before calling Scripts/build-mlx-metallib.sh directly.
  • VikerKit binary target resolution errors: clear SwiftPM caches or remove Package.resolved, then rerun swift package resolve.

GitHub Release Build

Scripts/release-github.sh archives the Xcode app, extracts Cormac.app, signs it with a Developer ID Application identity, notarizes and staples it, zips the final app, and uploads the zip to a GitHub release with gh.

NOTARY_PROFILE=cormac-notary Scripts/release-github.sh v0.1.0 --draft

The script auto-selects the first valid Developer ID Application identity in the Keychain unless SIGNING_IDENTITY is set, then infers DEVELOPMENT_TEAM from that identity for the Xcode archive step. NOTARY_PROFILE must name a notarytool keychain profile unless --skip-notarize is used. Use --skip-upload to stop after creating Build/Release/Cormac-<tag>.zip.

Agent Integration

Cormac supports two different ways to work with AI coding agents.

Terminal Agent Hooks

Terminal cards can run any CLI tool, but Cormac's notification hook integration is currently implemented for claude and codex when they are launched by command name from a Cormac terminal. Enable it in Settings > Agent Alerts > Enable CLI notification hooks, then open a new terminal card and run:

claude
codex

New terminal cards get Cormac-specific environment variables and wrapper directories prepended to PATH. The agent wrapper installs Cormac hooks into the agent's normal config, then launches the real CLI outside the wrapper path. Existing terminal cards keep their old environment, so open a fresh terminal after changing the setting.

Cormac also installs cormac and cedit wrappers inside Cormac's Application Support directory and exposes them to new terminal/ACP agent sessions through their environment. The commands are shell executables, so agents should invoke them through Bash:

cormac browser open https://slashdot.org
cormac browser state
cormac new_web_card --url https://example.com
cormac terminal_insert_text --text "ls -la"
cedit Sources/App.swift

cormac-browser and cormac-action remain available as legacy aliases. ACP coding-agent cards receive this environment directly and do not require personal skill files. Terminal-launched CLI agents can optionally discover the command through cormac skills; that write is disabled by default and must be enabled in Settings > Agent Alerts > Cormac Tool Discovery. When enabled, Cormac writes only under agent config directories that already exist: ~/.claude/skills/cormac/SKILL.md, ~/.codex/skills/cormac/SKILL.md, and ~/.config/opencode/skills/cormac/SKILL.md.

The helper can surface permission prompts, questions, idle/replied events, denied requests, and stop events as notifications and in-app attention highlights. By default Cormac uses macOS banners; enable Settings > Agent Alerts > Use Cormac floating notifications instead of macOS banners to show Cormac's own floating windows for both terminal hooks and ACP cards. Informational floating notifications offer OK to dismiss or Open to bring Cormac forward, select the originating workspace/card/pane, and scroll the card strip to it. Use Settings > Agent Alerts > Show Test Prompt to verify the floating prompt UI.

Current limits:

  • Only claude and codex have built-in wrappers.
  • Running an agent through an absolute path, alias, script, npx, or another wrapper can bypass Cormac's wrapper.
  • Other CLI agents still run normally, but they will not produce Cormac agent notifications until Cormac has hook support for them.

ACP Coding Agent Cards

ACP cards run agents through the Agent Client Protocol instead of a terminal shell.

  1. Open Settings > Coding Agents.
  2. Click Refresh Registry to install from the ACP registry, or Add Existing... to point at an ACP executable.
  3. Create a card with File > New Coding Agent... or the command palette entry New Coding Agent....
  4. Pick the configured client.

The card launches the ACP client over stdio, creates or loads an ACP session, and renders the transcript natively. Permission requests appear both inside the card and through Cormac's agent attention system. Agent replies can produce a notification/highlight when the turn completes, using either macOS banners or Cormac floating notifications depending on the Agent Alerts delivery setting.

Vim Bindings

Vim bindings are optional. Enable them in Settings > General > Enable Vim bindings for non-terminal cards. Terminal cards are never intercepted.

Use Ctrl+; to toggle between Insert and Normal mode. In Insert mode, keys pass through to the active card. In Normal mode:

  • Common navigation: j / k / h / l move or scroll; counts work, such as 5j.
  • Boundaries: gg jumps to the top, G to the bottom, 0 to the left/start, and $ to the right/end.
  • Paging: d pages down on navigable cards, and u pages up.
  • Activation/input: Enter activates the current item, i returns to Insert mode, and o focuses a card's primary input when it has one.
  • Visual/copy: v enters Visual mode on cards that support selection, and y copies the selected content.

Card-specific bindings:

  • Browser: Vimperator-style H / L go back/forward, r reloads, and o focuses the address bar.
  • Git Diff: Vimagit-style j / k moves the current file/hunk/line selection, s stages, S stashes, x or d discards with confirmation, c comments, y copies the prompt summary, and r refreshes.
  • File List: h goes up a directory, l or Enter opens the selected item, Visual mode selects multiple rows, and y copies selected paths.
  • Image/PDF: navigation scrolls or changes pages; Enter resets zoom.
  • Coding Agent: navigation scrolls the transcript; o or Enter focuses the composer.

Contributors

terhechte

180 commits

ben-iron

6 commits

terhechte/Cormac

13

stars

186

commits

Swift

primary language

Jul 1, 2026

updated

README

Cormac

Cormac is a macOS AppKit terminal host that embeds libghostty via a locally built GhosttyKit.xcframework.

Cormac runs unsandboxed (it spawns user shells, like any terminal emulator) with the Hardened Runtime enabled. See SECURITY.md for the full security model, including how agent file access is confined to the workspace.

Assets/cormac-workspace-browser.png

Project Layout

  • Vendor/ghostty: source checkout of ghostty-org/ghostty
  • Vendor/ghostty/macos/GhosttyKit.xcframework: native macOS Ghostty binary built from that source
  • Cormac.xcodeproj: canonical development project for the macOS app, signing, and SwiftUI previews
  • Sources/Cormac: the minimal AppKit host application
  • Sources/CormacACP: Agent Client Protocol card support
  • Sources/CormacAgentHook: helper used by terminal-agent notification hooks
  • Scripts/build-ghosttykit.sh: helper to rebuild the local Ghostty xcframework
  • Scripts/build-mlx-metallib.sh: helper used by Xcode and the SwiftPM bundler to build mlx.metallib
  • Scripts/make-app-bundle.sh: packages the SwiftPM executable as Build/Cormac.app with Ghostty resources

Development Build Setup

Cormac is currently set up for Apple Silicon macOS development. The project targets macOS 14.0 and Package.swift uses Swift tools 6.2, so use an Xcode release that includes the Swift 6.2 toolchain.

Prerequisites

Install the external tools used by the checked-in project and build scripts:

brew install libgit2 zig@0.15
  • Xcode: install the full Xcode app, not only the Command Line Tools. Make sure xcode-select points at it:
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
  • Metal Toolchain: Ghostty and MLX both compile Metal sources. If xcrun -sdk macosx metal --version fails, install the component:
xcodebuild -downloadComponent MetalToolchain
  • libgit2: Package.swift and Cormac.xcodeproj link -lgit2 from /opt/homebrew/opt/libgit2/lib. If Homebrew is installed somewhere else, update the library search paths in both places.
  • Zig: Ghostty requires Zig 0.15.2 or newer. Scripts/build-ghosttykit.sh automatically prefers /opt/homebrew/opt/zig@0.15/bin/zig; set ZIG_BIN=/path/to/zig if you need a different install.

Rust is not needed for normal Cormac development. Cormac consumes VikerKit 0.1.5 or newer from https://github.com/terhechte/VikerKit.git as a Swift package dependency; VikerKit provides the release-hosted editor engine xcframework and the reusable editor autosuggestion UI.

First Checkout

Clone with submodules:

git clone --recurse-submodules <repo-url>
cd Cormac

If you already cloned without submodules or LFS, fix the checkout in place:

git submodule update --init --recursive

Vendor/ghostty is the only top-level git submodule. SwiftPM dependencies such as VikerKit, MLX, Swift ACP, Markdown UI, and Hugging Face Transformers are resolved later by Xcode or swift build.

Build GhosttyKit

Build the native Ghostty library before opening or building Cormac:

./Scripts/build-ghosttykit.sh

This runs Ghostty's Zig build with:

zig build -Demit-xcframework=true -Demit-macos-app=false -Dxcframework-target=native -Doptimize=ReleaseFast

The script also checks that Zig is new enough and that Xcode's Metal compiler is available. The important outputs for Cormac are:

  • Vendor/ghostty/macos/GhosttyKit.xcframework
  • Vendor/ghostty/zig-out/share/terminfo
  • Vendor/ghostty/zig-out/share/ghostty

Re-run this script when Vendor/ghostty changes or when you need to rebuild the embedded Ghostty binary. You do not need to run it for every Swift edit.

Xcode Build

The preferred development flow is Xcode:

open Cormac.xcodeproj

Use the Cormac scheme to build, run, archive, or open SwiftUI previews. Xcode resolves Swift package dependencies into DerivedData automatically on first build.

The Xcode app target has two important run script phases:

  • Embed CormacAgentHook helper: builds/copies the helper into Cormac.app/Contents/MacOS/.
  • Build and embed mlx.metallib: runs Scripts/build-mlx-metallib.sh and writes mlx.metallib into the app resources.

So for Xcode builds, you do not manually compile the MLX Metal library. Let the build phase do it. The first build after package resolution can take longer because it has to compile the MLX kernels; incremental builds skip the work when the existing mlx.metallib is newer than the kernel sources.

SwiftPM Build

SwiftPM is useful for command-line builds, tests, and local bundling:

./Scripts/build-ghosttykit.sh
swift build
swift test

Bare swift build compiles the Swift products, but it does not produce a complete .app bundle with Ghostty resources. To create a runnable development bundle, use:

./Scripts/make-app-bundle.sh
open Build/Cormac.app

make-app-bundle.sh builds Cormac, builds CormacAgentHook, invokes Scripts/build-mlx-metallib.sh, and copies Ghostty's terminfo and shell-integration resources into the app bundle.

Running the bundled app gives Ghostty access to its terminfo database and shell-integration resources. The package intentionally starts as a single-window, single-surface terminal so it stays small and easy to customize. The host runtime and surface view live in Sources/Cormac/GhosttyRuntime.swift and Sources/Cormac/GhosttySurfaceView.swift.

Troubleshooting Build Setup

  • ld: library 'git2' not found: install libgit2 with brew install libgit2, or update the project/package library search paths if your Homebrew prefix is not /opt/homebrew.
  • Ghostty checkout not found: run git submodule update --init --recursive.
  • zig ... is missing, broken, or older than 0.15.2: run brew install zig@0.15, or set ZIG_BIN to a Zig 0.15.2+ binary.
  • cannot execute tool 'metal' due to missing Metal Toolchain: run xcodebuild -downloadComponent MetalToolchain.
  • MLX Metal kernels not found: let Xcode resolve packages once, or run swift build before calling Scripts/build-mlx-metallib.sh directly.
  • VikerKit binary target resolution errors: clear SwiftPM caches or remove Package.resolved, then rerun swift package resolve.

GitHub Release Build

Scripts/release-github.sh archives the Xcode app, extracts Cormac.app, signs it with a Developer ID Application identity, notarizes and staples it, zips the final app, and uploads the zip to a GitHub release with gh.

NOTARY_PROFILE=cormac-notary Scripts/release-github.sh v0.1.0 --draft

The script auto-selects the first valid Developer ID Application identity in the Keychain unless SIGNING_IDENTITY is set, then infers DEVELOPMENT_TEAM from that identity for the Xcode archive step. NOTARY_PROFILE must name a notarytool keychain profile unless --skip-notarize is used. Use --skip-upload to stop after creating Build/Release/Cormac-<tag>.zip.

Agent Integration

Cormac supports two different ways to work with AI coding agents.

Terminal Agent Hooks

Terminal cards can run any CLI tool, but Cormac's notification hook integration is currently implemented for claude and codex when they are launched by command name from a Cormac terminal. Enable it in Settings > Agent Alerts > Enable CLI notification hooks, then open a new terminal card and run:

claude
codex

New terminal cards get Cormac-specific environment variables and wrapper directories prepended to PATH. The agent wrapper installs Cormac hooks into the agent's normal config, then launches the real CLI outside the wrapper path. Existing terminal cards keep their old environment, so open a fresh terminal after changing the setting.

Cormac also installs cormac and cedit wrappers inside Cormac's Application Support directory and exposes them to new terminal/ACP agent sessions through their environment. The commands are shell executables, so agents should invoke them through Bash:

cormac browser open https://slashdot.org
cormac browser state
cormac new_web_card --url https://example.com
cormac terminal_insert_text --text "ls -la"
cedit Sources/App.swift

cormac-browser and cormac-action remain available as legacy aliases. ACP coding-agent cards receive this environment directly and do not require personal skill files. Terminal-launched CLI agents can optionally discover the command through cormac skills; that write is disabled by default and must be enabled in Settings > Agent Alerts > Cormac Tool Discovery. When enabled, Cormac writes only under agent config directories that already exist: ~/.claude/skills/cormac/SKILL.md, ~/.codex/skills/cormac/SKILL.md, and ~/.config/opencode/skills/cormac/SKILL.md.

The helper can surface permission prompts, questions, idle/replied events, denied requests, and stop events as notifications and in-app attention highlights. By default Cormac uses macOS banners; enable Settings > Agent Alerts > Use Cormac floating notifications instead of macOS banners to show Cormac's own floating windows for both terminal hooks and ACP cards. Informational floating notifications offer OK to dismiss or Open to bring Cormac forward, select the originating workspace/card/pane, and scroll the card strip to it. Use Settings > Agent Alerts > Show Test Prompt to verify the floating prompt UI.

Current limits:

  • Only claude and codex have built-in wrappers.
  • Running an agent through an absolute path, alias, script, npx, or another wrapper can bypass Cormac's wrapper.
  • Other CLI agents still run normally, but they will not produce Cormac agent notifications until Cormac has hook support for them.

ACP Coding Agent Cards

ACP cards run agents through the Agent Client Protocol instead of a terminal shell.

  1. Open Settings > Coding Agents.
  2. Click Refresh Registry to install from the ACP registry, or Add Existing... to point at an ACP executable.
  3. Create a card with File > New Coding Agent... or the command palette entry New Coding Agent....
  4. Pick the configured client.

The card launches the ACP client over stdio, creates or loads an ACP session, and renders the transcript natively. Permission requests appear both inside the card and through Cormac's agent attention system. Agent replies can produce a notification/highlight when the turn completes, using either macOS banners or Cormac floating notifications depending on the Agent Alerts delivery setting.

Vim Bindings

Vim bindings are optional. Enable them in Settings > General > Enable Vim bindings for non-terminal cards. Terminal cards are never intercepted.

Use Ctrl+; to toggle between Insert and Normal mode. In Insert mode, keys pass through to the active card. In Normal mode:

  • Common navigation: j / k / h / l move or scroll; counts work, such as 5j.
  • Boundaries: gg jumps to the top, G to the bottom, 0 to the left/start, and $ to the right/end.
  • Paging: d pages down on navigable cards, and u pages up.
  • Activation/input: Enter activates the current item, i returns to Insert mode, and o focuses a card's primary input when it has one.
  • Visual/copy: v enters Visual mode on cards that support selection, and y copies the selected content.

Card-specific bindings:

  • Browser: Vimperator-style H / L go back/forward, r reloads, and o focuses the address bar.
  • Git Diff: Vimagit-style j / k moves the current file/hunk/line selection, s stages, S stashes, x or d discards with confirmation, c comments, y copies the prompt summary, and r refreshes.
  • File List: h goes up a directory, l or Enter opens the selected item, Visual mode selects multiple rows, and y copies selected paths.
  • Image/PDF: navigation scrolls or changes pages; Enter resets zoom.
  • Coding Agent: navigation scrolls the transcript; o or Enter focuses the composer.

Contributors

terhechte

180 commits

ben-iron

6 commits

Languages

Swift

94.6%

HTML

2.0%

CSS

1.6%

JavaScript

1.0%