Skip to content

Install, upgrade, and remove Pohunek on macOS

Native macOS archives target Apple Silicon and macOS 14 or newer (aarch64-apple-darwin); Intel Macs and Rosetta shells are refused. macOS is not yet a published platform: public support is declared only when the final native acceptance gate passes. Release archives are ad-hoc signed, not notarized, and carry no Developer ID; every release asset has a GitHub build-provenance attestation. Everything runs as the logged-in owner: no sudo, no root service.

Archive Contents Installed by
pohunek-cli-<v>-aarch64-apple-darwin pohunek, completions, offline docs copy the binary onto PATH
pohunek-daemon-<v>-aarch64-apple-darwin pohunek, pohunekd, pohunek-sessiond, packaging/install-daemon.sh packaging/install-daemon.sh

Both archives also carry the README, the license, and the reference pages under docs/ (install.md, cli.md, and the others the README links to).

Every archive carries a MANIFEST (component, version, target, signing state signing adhoc, SHA-256 of each member) next to a .sha256 of the archive itself. The installers verify the manifest, the host, and member permissions before they run or change anything. A build made with packaging/macos/package --development is unsigned, named ...-unsigned-development, and not a release.

Terminal window
brew install zajca/pohunek/pohunek
pohunek setup

The tap is zajca/homebrew-pohunek and the formula is pohunek. It is a formula, not a cask, so Homebrew sets no quarantine attribute. It places pohunek, pohunekd, and pohunek-sessiond side by side and does no launchd work; pohunek setup registers the login agent (through pohunek service install) and reports the runtime packages and hooks this host can use.

  • After every brew upgrade pohunek, run pohunek service upgrade.
  • Before brew uninstall pohunek, run pohunek service uninstall.
Terminal window
shasum -a 256 -c pohunek-daemon-<v>-aarch64-apple-darwin.tar.gz.sha256
gh attestation verify pohunek-daemon-<v>-aarch64-apple-darwin.tar.gz --repo zajca/pohunek

After extracting, codesign --verify --strict <binary> succeeds and codesign -dvv <binary> shows Signature=adhoc with identifier io.github.zajca.pohunek.<name>. There is no Developer ID authority and no team identifier.

Download with curl -fLO (curl sets no quarantine attribute), verify the download as above, and extract it.

Extract the daemon archive anywhere owner-controlled (not a group- or world-writable directory) and run its installer:

Terminal window
./packaging/install-daemon.sh

The installer installs into ~/.local (POHUNEK_INSTALL_PREFIX selects another absolute prefix): <prefix>/libexec/pohunek/<version>/ holds pohunek, pohunekd, pohunek-sessiond, and the release’s runtime-catalog-anchor.json (mode 0644); <prefix>/bin/pohunek is the CLI. It writes ~/.config/pohunek/service.toml (mode 0600), registers the launchd login agent ~/Library/LaunchAgents/io.github.zajca.pohunek.<ns>.daemon.plist, and waits until the daemon answers on its socket. Put <prefix>/bin on PATH. Concurrent installers are serialized; an interrupted install resumes or rolls back when the installer is run again.

Terminal window
pohunek service status
pohunek doctor

pohunek doctor lists anything the host still lacks (login shell, Terminal, notifications, keychain, Privacy & Security access to project folders).

Install runtime packages from the release archive

Section titled “Install runtime packages from the release archive”

The daemon archive carries the official runtime packages of its release and the signed catalog that authorizes them. Install the official Pi, Claude Code, Codex, and Hermes packages from the extracted archive:

Terminal window
cd pohunek-daemon-<version>-aarch64-apple-darwin
pohunek plugin install runtime/packages/pi.tar.zst --catalog runtime/runtime-catalog.json --yes
pohunek plugin install runtime/packages/claude.tar.zst --catalog runtime/runtime-catalog.json --yes
pohunek plugin install runtime/packages/codex.tar.zst --catalog runtime/runtime-catalog.json --yes
pohunek plugin install runtime/packages/hermes.tar.zst --catalog runtime/runtime-catalog.json --yes

Verify the installation:

Terminal window
pohunek plugin list
pohunek host inspect local --json

See docs/install.md for details on package verification, profiles, and environment setup.

launchd starts the agent at every login and restarts it after a failure. It never starts session workers at login: each worker is its own job that the daemon registers for a live session.

Terminal window
pohunek service status --json
pohunek health --json

Restart only the daemon (live workers keep running):

Terminal window
launchctl kickstart -k gui/$(id -u)/io.github.zajca.pohunek.<ns>.daemon

Stop only the daemon agent with launchctl bootout gui/$(id -u)/io.github.zajca.pohunek.<ns>.daemon; workers keep running, and pohunek service status shows them. Never boot out the whole gui/$(id -u) domain: that ends every worker.

With Homebrew, run brew upgrade pohunek and then pohunek service upgrade. The upgrade first runs a read-only preflight of the live sessions and refuses (service_upgrade_sessions_at_risk) while one would lose recovery or not be adopted; see update after release. From an archive, extract the new daemon archive and run its installer again. The installer runs pohunek service upgrade, which stages the new versioned directory, probes every binary, and then restarts only the daemon agent. Live workers keep their process, PTY, and child, and keep running from the version directory they started in; that directory stays installed until no worker references it. Components that talk to each other must cross a protocol boundary together; see update after release.

Verify afterwards:

Terminal window
pohunek service status --json
pohunek session list --json

Each live session keeps its worker and child PID. If the upgrade is interrupted, pohunek service status --json shows a pending_transaction; running the installer again resumes or rolls it back. Do not delete a version directory or a worker definition by hand.

Rolling back is an upgrade to the previous archive: extract it and run its packaging/install-daemon.sh. Only do this when the older version understands the state the newer one wrote; the protocol and persisted-state boundaries in update after release are one-way, so after crossing one restore the complete newer component set instead.

Terminal window
pohunek service uninstall

refuses while sessions are live and names them; end them yourself, or accept the destructive path explicitly with pohunek service uninstall --stop-sessions. The default keeps the durable host identity, governance keys, session history, and worktrees. pohunek service uninstall --purge additionally removes the session store and its schema-migration backups (metadata.jsonl.pre-schema-<n>), event logs, worker journals, and host identity: it is never implicit.

  • Daemon and worker launchd output: ~/.local/state/pohunek/logs/launchd/.
  • Daemon structured logs: ~/.local/state/pohunek/logs/.
  • Worker definitions: ~/.local/state/pohunek/launchd/ (owner-private).

Locking the screen and closing a terminal do not stop workers. Logging out ends the launchd login domain and every worker with it; after the next login the daemon agent starts again and marks each of those sessions lost with reason runtime_lost. A reboot does the same. Sessions with a native resume reference can be recovered explicitly with pohunek session resume <id>. A sleeping Mac executes nothing; sessions continue when it wakes.

Archives are not notarized, so Gatekeeper blocks a file that carries the com.apple.quarantine attribute, which browsers set. Homebrew and curl -fLO downloads carry no quarantine attribute. Do not disable Gatekeeper and do not remove quarantine from a whole directory. Verify the checksum and the attestation first (see “Verify a download”), and only then remove the attribute from that one file:

Terminal window
xattr -d com.apple.quarantine <file>

An ad-hoc signature has no stable designated requirement, so after an upgrade macOS may ask again whether pohunek may access its pohunek-relay Keychain items. Choosing “Always Allow” applies to that build only; expect the prompt again after the next upgrade.