CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

What this is

Mytty is an Apple Silicon, macOS-native terminal (macOS 15+) for AI-assisted workflows. It embeds libghostty for terminal emulation/rendering and adds tabs, panes, agent status, an Attention Inbox, and session restoration — deliberately without a workspace abstraction. A companion iOS remote app lives in ios/MyttyRemote.

Commands

swift build                 # debug build (.build/debug/Mytty)
swift test                  # full test suite (also: make test)
swift test --filter "Codex session inspection"   # one suite/test by name
swift run Mytty             # run the dev app
scripts/bundle.sh debug     # packaged "Mytty Dev.app" in dist/ (make mac-app)
make ios                    # iOS Simulator build (regenerates Xcode project via xcodegen)
make ota                    # build MyttyRemote ipa/manifest.plist and deploy for OTA install (ad-hoc)
make release VERSION=x.y.z  # test, push main, tag vx.y.z -> CI builds/notarizes Mytty.zip
make release VERSION=x.y.z-beta.1   # same, but publishes a GitHub *pre-release*

A VERSION with a pre-release suffix (-beta.1, -rc.2, …) publishes a GitHub pre-release instead of a stable release: scripts/release.sh accepts exactly the versions ApplicationVersion can parse, and the Release workflow passes --prerelease to gh release create when the tag has a suffix. The in-app updater only offers pre-releases when the user Option-clicks Check for Updates; a plain check and the automatic launch/About checks stay on stable releases. Only the macOS app ships through this flow — the iOS remote app in ios/MyttyRemote is not part of the Mac release.

The iOS app ships through Xcode Cloud, which builds the checked-in ios/MyttyRemote/MyttyRemote.xcodeproj — it does not run xcodegen. After adding or removing a Swift file under ios/MyttyRemote, run make ios and commit the regenerated project.pbxproj together with the file, or the Xcode Cloud build fails with "Cannot find … in scope".

One-time prerequisite after cloning or after changing the Ghostty revision (needs Homebrew zig@0.15):

git submodule update --init --recursive
scripts/build-ghostty.sh    # builds Vendor/ghostty/macos/GhosttyKit.xcframework

Architecture

SwiftPM package with strict target boundaries (Package.swift):

Remote panes: a Mac can mirror a terminal on another paired Mac. That pane is its own SplitNode.remote case, not a terminal — it has no local process, working directory or Ghostty surface, so agent polling, repository status and mytty-ctl all skip it deliberately. RemotePaneConnectionCoordinator owns one connection per host app-wide (not per window). Pairing is by link: the host copies a mytty://pair?... URL from Settings › iOS Remote Access, the client pastes it under Settings › Remote Macs. See docs/how-to/open-a-pane-on-another-mac.md.

Agent integration model

Agent state is event-driven, never scraped from terminal output. Enabling a provider in Settings installs hooks into that provider's global config (~/.codex/hooks.json, ~/.claude/settings.json, etc.); the hooks call mytty-agent-hook, which sends versioned, idempotent events over a pane-scoped Unix socket (each surface gets its own MYTTY_* environment). An append-only event log plus a pure reducer derive the run state; Attention policy derives inbox items from that. See docs/reference/agent-event-protocol.md and docs/reference/agent-providers.md (the latter documents the per-provider lifecycle mapping and status-bar data sources).

Complementing the hooks, TerminalWindowController's AgentStatusPollingCoordinator polls each pane's foreground process (0.5s) and:

AgentUsagePollingCoordinator loads quota/cost meters the same way, via NativeAgentUsageLoader and the parallel AgentProviderUsageSource registry over each provider's *UsageProbe (MyTTYCore).

AgentSessionDatabase (MyTTYCore) is the shared read-only SQLite helper; it falls back to an immutable=1 connection for WAL databases whose sidecars were checkpointed away — reuse it instead of opening SQLite directly.

Workflow rules

Conventions that matter here