Install
Tenon is a universal macOS app. It needs macOS 14 or later and nothing else — no JavaScript toolchain, no runtime to install first.
Download a release
Get the newest build from Releases.
Extract in Finder or with ditto, never with unzip
This is the single most common way a good download turns into an app macOS refuses to open.
The archive stores the extended attributes that Tenon's code signature seals over. Finder restores them, and so does ditto. Info-ZIP's unzip cannot, so it writes them out as 735 stray ._* files inside the bundle instead, and Gatekeeper then rejects the app with "a sealed resource is missing or invalid". That reads like a corrupt download. It is a corrupt extraction.
ditto -x -k Tenon-*-macos.zip . # or just double-click it in Finder
mv Tenon.app /ApplicationsCheck what you downloaded
The build is signed with a Developer ID certificate, hardened, notarized and stapled, so a machine that has never seen Tenon verifies it offline — with no right-click-to-open dance. Confirm that for yourself:
shasum -a 256 Tenon-*-macos.zip # compare with SHA256SUMS on the release
spctl --assess -vv /Applications/Tenon.appThe second command should say accepted and source=Notarized Developer ID.
Releases are marked pre-release
That is deliberate while Tenon is pre-alpha: interfaces still change between builds. Read the changelog before upgrading a setup you rely on.
Homebrew
A cask is generated with every release. brew install --cask starts working once the repository is reachable without credentials, since Homebrew fetches anonymously.
Build from source
You need macOS 14+ and Xcode. You do not need to install a project generator or fetch libghostty yourself — the verbs do it, with the exact pinned versions and checksum verification.
git clone https://github.com/nguyenvanduocit/tenon.git
cd tenon
./tenon dev # build Debug and launch
./tenon install --launch # build Release and install to /Applications./tenon on its own lists every verb, and each one fetches the build inputs it needs, so a fresh clone has nothing to set up first. The first run downloads a pinned GhosttyKit.xcframework — about 130 MB — and caches it.
That local install is signed ad-hoc, which is all a machine needs to run software it just compiled itself. ./tenon install --staging puts a second copy beside the first under its own identity, so you can exercise a candidate build without replacing the one you work in.
Going around the verbs
swift build, swift test and xcodebuild do not fetch anything themselves. On a clone that has never run a verb, run ./scripts/internal/setup-ghostty.sh first or they will not find the framework.
Install the CLI
tenon-cli is how a shell — or an agent sitting in a pane — talks to the running app. It ships inside the app bundle:
ln -s /Applications/Tenon.app/Contents/MacOS/tenon-cli ~/.local/bin/tenon-cli
tenon-cli pingA healthy answer names the running instance and its wire version:
{
"active" : false,
"build" : "1",
"pid" : 59949,
"protocolVersion" : 3,
"socketPath" : "/tmp/tenon-501/tenon.sock",
"version" : "0.1.0"
}Inside a Tenon terminal you do not have to point it anywhere: every pane exports TENON_SOCKET_PATH and TENON_PANE_ID, and the CLI defaults to them. See Driving Tenon from a terminal.
First launch
Tenon opens on a workspace and one full-size terminal. Which directory it picks:
TENON_WORKSPACE_PATH, if set;- otherwise a meaningful launch directory;
- falling back to your home directory when LaunchServices starts the app at
/.
Next: Your first workspace.