A native macOS terminal, written in pure Rust, that AI agents can work in without seeing your secrets.
Warning
Work in progress. hefyn is under active development and only early pre-releases exist. Things break, and settings and the MCP API can change without notice.
Download hefyn-<version>-macos-universal.dmg from Releases, open it and drag
hefyn to Applications. The build is universal (Apple silicon and Intel) and needs macOS 13
or newer.
Pre-releases are not notarized yet, so Gatekeeper refuses the first launch. Right-click
hefyn.app and choose Open, or run xattr -dr com.apple.quarantine /Applications/hefyn.app.
Or build from source (see Getting started):
cargo run -p hefyn-app --releaseVerify a downloaded dmg against the .sha256 file next to it:
shasum -a 256 -c hefyn-<version>-macos-universal.dmg.sha256hefyn is a GPU-rendered terminal built on GPUI and alacritty_terminal.
It organizes many shells and SSH connections in a session tree and has a built-in MCP gateway.
An agent such as Claude Code can list sessions, read output, run commands and edit files in the
sessions you allow, and everything it gets back has been redacted first. Sensitive values reach
the agent as variable names it can pass back, never as the values themselves.
| Alacritty grid, truecolor, IME input, mouse reporting, selection, scrollback, bracketed paste and OSC 52. Links open with ⌘-click. Native window with the traffic lights in hefyn's own top row; no web view anywhere. | Shell integration for zsh, bash and fish groups every command with its output, with a context line showing the directory, git branch and changed files. Your dotfiles are never modified. |
| Groups, screens and sessions with icons, emoji and accent colors. Sort them with drag and drop or A→Z and navigate with the keyboard. The tree and your open tabs come back on every launch. | Split side by side (⌘D) or top and bottom, then zoom, resize and cycle panes. Split sessions become a screen in the tree, so the layout comes back exactly as you left it. |
Hosts from ~/.ssh/config as quick picks, jump hosts, reconnect, and command
blocks on the server without installing anything there
(⌘⇧K).
|
Opt-in per session or group. Each connection has its own key, access level, rate limit, command rules and folder lock. Risky commands wait for your approval. |
| Everything an agent reads is redacted by regex rules, an entropy detector and a local GLiNER model. If a detector fails, nothing is sent. |
A detected value reaches the agent as {{var:EMAIL_1}}. The agent passes the
name back in commands and file edits and hefyn puts the real value in. Keychain secrets
work the same way with {{secret:NAME}}.
|
read_file and edit_file let agents change code with exact
replacements, written directly and atomically. Every edit appears in the session as a
card with a diff: ✎ Edited src/limit.rs +3 −1.
|
Before an agent changes anything, hefyn snapshots the working directory. Revert… on any agent block or edit restores it; the agent history (⌘⇧H) lists every call. |
| JSON themes and settings reload while the app runs. Custom SVG icons, an editable keymap with a searchable cheat sheet (⌘/) and a command palette (⌘⇧P). | One native binary, GPU-rendered with GPUI. The redaction model runs in-process; nothing leaves your machine unless you connect an agent. |
An agent's commands and edits run in a visible terminal. You see the real output; the agent gets {{var:…}} names instead of sensitive values.
Left: command blocks with the git context, duration and exit status. Right: hefyn Dark with the sidebar collapsed to a rail and an agent's edits as diff cards.
cargo run -p hefyn-app --release # run hefyn
scripts/bundle-macos.sh --release # build target/bundle/hefyn.appRequires Rust 1.90+ on macOS. bundle-macos.sh also needs rsvg-convert and iconutil.
bundle-macos.sh also takes --universal (arm64 + x86_64), --dmg and --notarize. Without
MACOS_SIGN_IDENTITY the bundle is ad-hoc signed.
The config dir is ~/Library/Application Support/dev.hefyn.hefyn. Set HEFYN_CONFIG_DIR to use a
different one. See keyboard shortcuts and themes and appearance.
- Choose New SSH connection… (
⇧⌘K, the server button in the sidebar header, the command palette, or a group's context menu). Hosts from your~/.ssh/configare offered as quick picks; you can also pasteuser@host:port, anssh://address or a wholessh -p 2222 hostcommand into Host and the other fields fill in. Key file, jump host, start directory and a custom name are there too. - Press Connect. The connection is a normal session (server icon, a green dot while it is
up) whose tab shows
user@host:~/dir. Splitting it opens another connection to the same server in the same directory. When the connection closes, the pane offers Reconnect; Edit connection… in the session's context menu changes the target. - Let an agent work on this server (in the same dialog, off by default) shares the new session with an agent at the access level you pick and shows the one-line setup. The agent's commands run on the server through that connection; risky ones still ask for your approval and output is redacted before the agent sees it.
Command blocks, exit codes and the current directory work on the server for bash and zsh: hefyn
brings its shell integration along for this session only and writes nothing to the server (switch
it off per connection under Advanced, or by default in Settings → Terminal → SSH). The same
integration applies when you type ssh host in an ordinary session.
- Right-click a session or group and choose Agents → Share with agent… (also the shield button in the sidebar header, or Share session with agent… in the command palette).
- Pick what the agent may do (Read-only looks around and runs read-only commands like
ls,cat,grep; Read & write also edits files and runs commands, asking you before anything risky) and click Create connection. The agent stays inside the session's folder and everything below it, never above. - Click Copy setup command and paste it into your shell. The dialog shows "Waiting for the agent to connect…" and flips to "Connected" on its first request:
claude mcp add --scope user --transport http hefyn-<name> http://127.0.0.1:47800/mcp/<binding-id> \
--header "Authorization: Bearer <key>"Other MCP clients can use Copy JSON. Settings → Agents lists every connection with its status ("Active", "Used 5 min ago", "Not used yet"), lets you rename it, switch it off, change what it reaches, copy or rotate its key, and edit the access levels.
Security model: what happens on every agent request
- Transport checks. The gateway binds only to loopback and rejects foreign
Host/Originheaders, which protects against DNS rebinding. An unknown, disabled or wrong-token binding gets the same401. - Scope. A binding governs one session or one group. The most specific enabled binding wins, so every session is governed by at most one endpoint. Sessions outside the scope are reported as unknown.
- Policy. The policy is re-read on every call, so edits apply immediately. It covers:
- capabilities: list, read, run, write files, send input;
- a per-binding rate limit;
- command rules for
run_command:- newlines, shell operators (
; & | $> < ( ) `) and privilege escalation (sudo,su,doas, …) are rejected; - deny globs always win, and they match case-insensitively;
- the default deny list covers secret files for every reader (
.ssh,.aws,.netrc, …) and flags that turn read-only tools into command runners (find -exec,rg --pre,--output); - project
.envfiles are an ask rule instead: reading one needs your approval, and the output is still redacted. Thelist_env_keystool returns only the variable names in an env file (inside the folder lock) and needs no approval. - Folder lock on SSH sessions. The folder is the connection's Start directory (or the
first folder the server shell reports). Paths are checked lexically (
.., sibling folders such aswp-content-old,~are refused) and, through hefyn's remote shell integration, again on the server with symbolic links resolved. Programs whose files cannot be known from their arguments (php, python, wp, bash, ...) need your approval; Read-only never runs them. Without command blocks (integration) on the connection the lock refuses to run anything.write_fileandedit_filewrite text files inside the folder (never git hooks). In local sessions hefyn writes the file itself, atomically, after the same checks and with symbolic links refused; over SSH the content is typed through the connection.
- newlines, shell operators (
- Redaction, failing closed. All text returned to the agent is redacted. If any detector errors, the call fails and no text is sent. Output is redacted in whole lines with context before the cursor, and a still-incomplete trailing line is held back, so a secret is never split across two reads.
- Guards (optional). laya checks commands and output. If a guard is enabled but unavailable, commands are denied and output is withheld.
- Audit. Every call is logged with its outcome. Commands are logged in redacted form, and raw output is never logged.
send_input gives full control, because typed text can run anything. It is off by default, and
the UI warns before you turn it on.
Agents never see sensitive values, but they can still use them.
- Detected values. When output contains something a detector flags (an email address, a name,
an API key, a high-entropy token, …), the agent sees a placeholder such as
{{var:EMAIL_1}}or{{var:API_KEY_1}}instead. The same value keeps the same name for the whole session, and the values live only in memory until hefyn quits. - Using them. The agent passes the placeholder verbatim in
run_command,write_fileoredit_file, and hefyn substitutes the real value at the last moment (as one quoted word in commands, as plain text in files). Output that echoes it is redacted again. - When hefyn asks you. Putting a value back into the file it came from is free, so an agent can read a file with secrets, edit it and write it back without destroying them. Using a value anywhere new (another file, a command) asks you once per variable and session.
- Your own variables.
set_variablestores a named value per session;capture_redactiongives a detected value a friendlier name. - Keychain secrets. Secrets you add under Settings → Secrets are used as
{{secret:NAME}}by connections you granted them to. They are never written into files, except back into a file that already contains them.
| Tool | What it does |
|---|---|
list_sessions |
Sessions the connection may see, with folder and access level. |
read_screen, read_output |
The visible screen or the output history, redacted. |
run_command |
Runs one command line in the session (rules, folder lock, approval, snapshot). |
read_file |
A file with line numbers, redacted. Same rules as cat on that path. |
edit_file |
Exact search-and-replace edits, applied all-or-nothing, shown in the session as a diff. |
write_file |
Creates, overwrites or appends a text file inside the folder lock. |
list_env_keys |
The variable names in a .env file, never the values. |
list_secrets, request_secret |
Keychain secrets granted to the connection; ask you for a new one. |
set_variable, capture_redaction, list_variables, delete_variable |
Session variables. |
send_input |
Types keys into the session. Off by default; it gives full control. |
- GLiNER (default feature
gliner) runs in-process viagline-rs. Placetokenizer.jsonandonnx/model.onnxfrom e.g.knowledgator/gliner-pii-edge-v1.0in<config dir>/models/gliner. Models are never bundled. - laya (feature
laya) talks to a locallaya-serve(pip install "laya[serve]"), configured vialaya_urlinsettings.json.
For maintainers. Pushing a v* tag runs .github/workflows/release.yml, which builds a universal
dmg, signs it, notarizes it and attaches it to a GitHub Release together with its SHA-256.
git tag v0.1.0 && git push --tagsRepository secrets (Settings, Secrets and variables, Actions):
| Secret | Value |
|---|---|
MACOS_CERTIFICATE_P12_BASE64 |
Developer ID Application certificate with private key, exported as .p12, base64-encoded (base64 -i cert.p12 | pbcopy). |
MACOS_CERTIFICATE_PASSWORD |
Password chosen when exporting the .p12. |
MACOS_SIGN_IDENTITY |
Full identity, e.g. Developer ID Application: Name (TEAMID). |
APPLE_ID |
Apple ID used for notarization. |
APPLE_TEAM_ID |
10-character team ID. |
APPLE_APP_PASSWORD |
App-specific password from appleid.apple.com. |
If any of them is missing, the workflow still builds an ad-hoc signed, non-notarized dmg and marks
the release as a pre-release. The tag version must match version in Cargo.toml, since the dmg
name and CFBundleShortVersionString come from there.
Locally: MACOS_SIGN_IDENTITY="..." NOTARY_KEYCHAIN_PROFILE=hefyn scripts/bundle-macos.sh --release --universal --dmg --notarize
(create the profile once with xcrun notarytool store-credentials hefyn).
cargo test --workspace
cargo clippy --workspace --all-targets -- -D warningsEnd-to-end UI tests use a port of gpui-agent in
third_party/gpui-agent. It runs in offline scripted mode only, so nothing leaves the machine:
scripts/ui-test.sh tests/ui/new-session.json tests/ui/new-session-script.jsonThe script builds with a patched gpui-pre that exposes the accessibility tree. The patch is
applied via --config for that build only, and Cargo.lock is restored afterwards. Tests run
against a throwaway config directory.
Workspace layout
| Crate | Purpose |
|---|---|
hefyn-app |
The hefyn binary: window, sidebar, tabs, panes, palette, settings. |
hefyn-core |
Session tree, ids, icons, layouts, atomic JSON persistence. No UI dependencies. |
hefyn-term |
PTY (portable-pty) + alacritty_terminal grid, shell integration, xterm key encoding. |
hefyn-terminal-view |
GPUI terminal element and view: rendering, blocks, input, mouse, selection. |
hefyn-policy |
MCP bindings, scopes, capabilities, command rules, tokens. |
hefyn-redact |
Redaction pipeline (regex, entropy, GLiNER) and the laya Guard. |
hefyn-mcp |
Streamable-HTTP MCP server (rmcp) enforcing policy and redaction. |
hefyn-vault |
Keychain-backed secrets that agents can use but never read. |
hefyn-undo |
Working-directory snapshots and restore for agent commands. |
hefyn-extension |
Extension API (internal for now; see the roadmap). |
hefyn is focused on being a great terminal first. Coming later:
- Extensions: palette snippets, input rules, status items and custom redaction patterns via
a declarative
extension.json.
Apache-2.0. See NOTICE for attributions: the terminal rendering, key encoding
and shell integration are adapted from tty7 (Apache-2.0), and
third_party/gpui-agent is MIT.