AI Decision System
Replayable, offline-verifiable records for AI-assisted go/no-go decisions: experiment promotions, typed model answers and human-approved actions.
翻译准备中
A fast macOS terminal for working with coding agents, built on libghostty.
翻译准备中
取自仓库 README
正在加载完整 README。 以下为已保存的摘录。
A fast macOS terminal for working with coding agents.
Run several coding agents side by side, each in its own session, and Farol shows you which one is working, which one is done and which one needs you. Farol is Portuguese for lighthouse.
Underneath is libghostty, the engine behind Ghostty, so it is as fast as Ghostty and reads your Ghostty config.
Farol is early. It works as a daily terminal, but expect rough edges. Questions and ideas are welcome on Discord.查看完整 README ↗Run several coding agents side by side, each in its own session, and Farol shows you which one is working, which one is done and which one needs you. Farol is Portuguese for lighthouse.
Underneath is libghostty, the engine behind Ghostty, so it is as fast as Ghostty and reads your Ghostty config.
Farol is early. It works as a daily terminal, but expect rough edges. Questions and ideas are welcome on Discord.
Or with Homebrew:
brew install --cask snowztech/tap/farol
It needs a Mac with Apple Silicon and macOS 14 or later. Releases are signed and notarized, so macOS opens Farol without warnings. To build from source instead, see Build from source.
When a new version is out, an Update button shows in the title bar. Click it and Farol installs the update and relaunches, with your sessions and agents back. brew upgrade --cask farol works too.
New task. ⇧⌘N asks what to do, then starts Claude Code or Codex on it in a new worktree, with the task as its first prompt. Or pick one of your open Jira tickets or the repo's GitHub issues, and the branch is named after it and the agent gets the ticket in full. With more than one Claude Code or Codex account, each one is a choice. From a folder of repos, or with Add repositories from inside one, tick the repos the task touches. Choose repository… adds a Git repo from anywhere on the Mac. Each selected repo gets a worktree on the same branch, side by side in one folder, and the agent starts there. The review and graph panels get a tab per repo with its changes, and the title bar counts them all.
Agent status. Each session shows whether its agent is working, waiting for you or done, with a notification when it waits in the background. Claude Code, Codex, OpenCode and Pi connect in one click, and any other agent can report with farol status. See Agent status.
Agents from any app. Farol's lighthouse in the menu bar lights up while agents work, wait or finish, and its menu opens any session. Or pick a small status panel at the notch or on the screen edge, which you can drag anywhere along the side.
From your phone. Farol for iPhone lists your sessions and opens each one as its agent's conversation. Approve or answer with a tap, get a notification when an agent waits, and keep count on the lock screen. End-to-end encrypted. See From your phone.
Worktree sessions. ⇧⌘T asks for a branch and opens a session in its own git worktree, so parallel agents never touch each other's files. Closing it offers to remove the worktree and always keeps the branch.
Sessions in a sidebar. Open as many as you like. Hidden sessions keep running but stop rendering, so twenty background agents cost no GPU time. Each row shows its git branch and the ports its programs listen on, like :3000, which open in your browser. While an agent waits, the row says what for. Settings → Appearance can group sessions under each project's name. Double-click to rename, drag to reorder. Click the window title or press ⌘P to search every session and switch, even with the sidebar closed. Keep typing to find a file or a folder around you: a file opens next to the terminal, a folder opens as a new session. It also finds the commands your project defines in its Makefile, package.json or justfile, like make test or npm run dev, and Return runs one in the pane you are in, or in a new pane below when an agent is working there. Add your own from the pane's Run menu. Save an editable script in the checkout's .farol/commands/ folder to share it with the project, or in ~/.farol/commands/ to use it in every project. Global commands run in the active pane's folder. A project command with the same name takes priority.
Files next to the terminal. ⇧⌘E opens the session's project as a tree, with ignored files dimmed. Click a file to open it in a pane beside the terminal, with syntax colors from your theme, line numbers and ⌘F, and make a quick fix with ⌘S to save. ⌘-click a path in the output, like src/app.ts:42 from an agent, and it opens there at that line. When an agent changes the file you have open, it reloads, or asks first if you have unsaved edits. The tree updates as agents add files, and costs nothing while it is closed.
Review what the agent did. The title bar shows what the session changed, like ± +821 −61, and only while there is something to see. Click it, or ⌥⌘R, for every changed file as one scrolling diff, uncommitted work or everything since the branch left main or any other branch. Open a file from there to land on its first change and fix it. Then commit, push and open a pull request on GitHub or a merge request on GitLab, without leaving the panel.
Git graph. ⌥⌘G, or the icon next to the change count, shows the session's branches and commits as a colored graph. Click a branch to check it out. Right-click to create, rebase or delete a branch, or to cherry-pick or revert a commit. Fetch, update and push branches from there too.
Resolve conflicts. When a merge, rebase, cherry-pick or revert stops on conflicts, the title bar and the session's row say so, even when an agent started it in the background. ⌥⌘M shows the files in three columns: your version, the result and the incoming one. Each change has an arrow to take it and a cross to leave it out, changes only one side made are taken from the start and folded away, and the result can be edited by hand. Mark each file resolved with ⌘S, run the tests, then continue, skip or abort from the same place.
Split panes. ⌘D splits a session, and the new pane opens in the same folder. ⇧⌘R gives a pane a name, like "back" or "tunnel". Layouts and names come back after a relaunch.
Find in scrollback. ⌘F searches the focused pane and shows the match count.
Ghostty inside. Same rendering, speed and escape sequence support, and your Ghostty config is loaded.
Themes and settings. Farol's own four themes, in the colors of the app icons and with Farol Beam as the default, plus Ghostty's 600+ themes with live previews, and settings inside the window. Settings → Appearance → Style can put each panel in a rounded card, with or without your theme's color.
Copy and paste, accents and input methods. Pasting text that could run commands asks first, files and text can be dropped on the terminal, dead keys compose as you type, and input methods for other languages work at the cursor.
Commit an executable .farol/setup to prepare each new worktree. Give it a shebang and run chmod +x .farol/setup. Farol copies ignored .env files first if enabled in Settings → Worktrees, then runs setup in the terminal before starting the agent or chosen command. If setup fails, the worktree stays open and the agent does not start. An optional .farol/teardown runs when you remove the worktree. The task docs show an example and explain retries and tasks across repos.
Coming next: pipeline status and review comments in the sidebar, tasks from GitLab issues, and sessions that show their ticket. See ROADMAP.md.
The dot next to each session shows what its agent is doing:
| State | Dot | Meaning |
|---|---|---|
| Idle | Hollow gray | No active agent state |
| Working | Breathing cyan | The agent is processing the prompt or using tools |
| Waiting | Yellow | The agent needs approval or an answer |
| Done | Solid green | The turn finished while you were elsewhere |
The colors come from your theme's own cyan, yellow and green, so they always match it.
When an agent waits or finishes while Farol is in the background, you get a notification, and the Dock icon counts the sessions that are waiting. To see agents working from any app too, turn on the menu bar item or the status panel, at the notch or on the screen edge, in Settings → Agents → Outside Farol.
Claude Code: open Settings → Agents and click Connect. Farol adds its hooks to ~/.claude/settings.json, leaves everything else in the file alone and keeps a backup. It also adds a skill in ~/.claude/skills/farol, so you can ask Claude Code to change a Farol setting, like the theme or the font, and it edits ~/.config/farol/config for you. If you have more than one account, in folders like ~/.claude-work, each one gets its own row. Disconnect removes only Farol's hooks and its skill, and Claude Code keeps working as before.
Codex: interactive sessions work automatically through Codex's terminal titles. Keep Codex's terminal-title setting enabled. To cover non-interactive runs and background agents too, click Connect. Farol adds hooks to ~/.codex/hooks.json, leaves other hooks alone and keeps a backup. Restart open Codex sessions, then review and trust the hooks with /hooks. Connecting adds the same skill in ~/.codex/skills/farol. Disconnect removes only Farol's hooks and its skill. Interactive status keeps working.
OpenCode and Pi: click Connect and Farol adds one plugin file, ~/.config/opencode/plugins/farol.js or ~/.pi/agent/extensions/farol.ts, plus the same skill. Restart open sessions to load it. OpenCode reports working, done, and what a permission or a question asks. Pi reports working and done, and the phone reads its conversation. Farol keeps the plugin up to date and Disconnect removes it. They show in Settings once their config folder exists.
Other agents can report with "$FAROL_CLI" status working|waiting|done|clear, which Farol makes available in every session. Agents that ring the terminal bell show as waiting without any setup.
Nothing to install: your agent runs farol status when something happens in it, from its hooks, a plugin or an extension. Farol puts FAROL_CLI in every session's environment, and it is unset elsewhere, so check for it and do nothing outside Farol.
| When your agent | It runs | Needed |
|---|---|---|
| Starts working on a prompt | "$FAROL_CLI" status working | Yes |
| Ends its turn | "$FAROL_CLI" status done | Yes |
| Waits for an approval or an answer | "$FAROL_CLI" status waiting --tool Bash --asking "npm test" | For the yellow dot |
| Quits, or is interrupted | "$FAROL_CLI" status clear | No |
| Starts or resumes a conversation | "$FAROL_CLI" status start --session "$ID" --log "$LOG" | No |
--asking is what the sidebar, the notification and the phone show while it waits. --tool is optional.--prompt "<text>" on working shows what you asked before the agent's log has it.--session and --log together name the conversation, so Farol can resume it after a relaunch. The phone reads the log when Farol knows its format.A hook that runs a shell command can be one line:
[ -z "$FAROL_CLI" ] || "$FAROL_CLI" status done
Farol's own OpenCode and Pi plugins, in Sources/FarolCore/AgentPlugin.swift, are short examples in JavaScript.
{
"hooks": {
"UserPromptSubmit": [{ "hooks": [{ "type": "command", "command": "[ -z \"$FAROL_CLI\" ] || \"$FAROL_CLI\" status working" }] }],
"PostToolUse": [{ "hooks": [{ "type": "command", "command": "[ -z \"$FAROL_CLI\" ] || \"$FAROL_CLI\" status working" }] }],
"Notification": [{ "matcher": "permission_prompt|elicitation_dialog", "hooks": [{ "type": "command", "command": "[ -z \"$FAROL_CLI\" ] || \"$FAROL_CLI\" status waiting" }] }],
"PermissionRequest": [{ "matcher": "^AskUserQuestion$", "hooks": [{ "type": "command", "command": "[ -z \"$FAROL_CLI\" ] || \"$FAROL_CLI\" ask", "timeout": 86400 }] }],
"Stop": [{ "hooks": [{ "type": "command", "command": "[ -z \"$FAROL_CLI\" ] || \"$FAROL_CLI\" status done" }] }],
"SessionEnd": [{ "hooks": [{ "type": "command", "command": "[ -z \"$FAROL_CLI\" ] || \"$FAROL_CLI\" status clear" }] }],
"SubagentStart": [{ "hooks": [{ "type": "command", "command": "[ -z \"$FAROL_CLI\" ] || \"$FAROL_CLI\" status subagent-start" }] }],
"SubagentStop": [{ "hooks": [{ "type": "command", "command": "[ -z \"$FAROL_CLI\" ] || \"$FAROL_CLI\" status subagent-stop" }] }]
}
}
Outside Farol $FAROL_CLI is unset, so the hooks do nothing. The PermissionRequest hook for AskUserQuestion lets you answer Claude's questions in Farol. It waits for your answer, and when Farol can't answer, Claude asks in the terminal as usual. The Notification matcher leaves out Claude's idle reminder, which would otherwise mark a finished session as waiting. The Subagent hooks keep a session working while a background agent runs after Claude's turn ends.
In ~/.codex/hooks.json:
{
"hooks": {
"SessionStart": [{ "matcher": "startup|resume|clear", "hooks": [{ "type": "command", "command": "[ -z \"$FAROL_CLI\" ] || \"$FAROL_CLI\" status clear" }] }],
"UserPromptSubmit": [{ "hooks": [{ "type": "command", "command": "[ -z \"$FAROL_CLI\" ] || \"$FAROL_CLI\" status working" }] }],
"PostToolUse": [{ "hooks": [{ "type": "command", "command": "[ -z \"$FAROL_CLI\" ] || \"$FAROL_CLI\" status working" }] }],
"PermissionRequest": [{ "hooks": [{ "type": "command", "command": "[ -z \"$FAROL_CLI\" ] || \"$FAROL_CLI\" status waiting" }] }],
"PreToolUse": [{ "matcher": "^request_user_input$", "hooks": [{ "type": "command", "command": "[ -z \"$FAROL_CLI\" ] || \"$FAROL_CLI\" status waiting" }] }],
"Stop": [{ "hooks": [{ "type": "command", "command": "[ -z \"$FAROL_CLI\" ] || \"$FAROL_CLI\" status done" }] }],
"Interrupt": [{ "hooks": [{ "type": "command", "command": "[ -z \"$FAROL_CLI\" ] || \"$FAROL_CLI\" status clear" }] }],
"SessionEnd": [{ "hooks": [{ "type": "command", "command": "[ -z \"$FAROL_CLI\" ] || \"$FAROL_CLI\" status clear" }] }],
"SubagentStart": [{ "hooks": [{ "type": "command", "command": "[ -z \"$FAROL_CLI\" ] || \"$FAROL_CLI\" status subagent-start" }] }],
"SubagentStop": [{ "hooks": [{ "type": "command", "command": "[ -z \"$FAROL_CLI\" ] || \"$FAROL_CLI\" status subagent-stop" }] }]
}
}
These hooks complement the terminal-title status used by interactive Codex sessions. Outside Farol $FAROL_CLI is unset, so the hooks do nothing. Codex 0.159.2 or later is required because earlier versions did not pass Farol's pane environment to hooks.
An agent in Farol can see the other sessions and, when you let it, hand work to another agent. The farol command is in every Farol pane:
farol sessions # each session: id, status, name, agent, branch, and what one that waits asks
farol task add a search box --agent codex # a new agent on the task, in its own worktree and branch
farol wait 8f3b99d0 # returns once that agent is done or needs someone
farol read 8f3b99d0 # its last message
farol send 8f3b99d0 run the tests # types a message to it, as you would
A session is its id, the start of it, or its name. --json gives a program every field. Reading is always allowed. Starting tasks and sending messages need Settings → Agents → Let agents start tasks and message sessions, since one agent then acts on others. Farol never types into a session that waits for someone, since that answer is yours, and a task opens beside the session you are in without taking the window.
The skill Farol adds when you connect an agent teaches it these commands, so you can say "split this into two tasks and review what each did". Codex runs its commands in a sandbox that cannot reach Farol, so it asks you before running farol. Allow commands that start with farol once and it stops asking.
Farol for iPhone, in beta on TestFlight, lets you answer your agents when you are away from the Mac. In Farol, open Settings → Remote, turn it on, press Show code and scan it from the app.
The phone lists your sessions with their status and opens each one as its agent's conversation, with the terminal one tap away. Approvals and questions show up as buttons. You can reply with a slash command or a picture, look over what changed and start a new session. It notifies you when an agent waits or finishes, and widgets and a Live Activity keep count while the app is closed.
Everything between the Mac and the phone is encrypted end to end, so the relay that joins them cannot read it. The protocol, the relay and farol-host live in farol-remote.
The agents run on the Mac, so it has to stay on. To close the laptop, run them in tmux on a server with farol-host:
curl -fsSL https://farol.sh/host.sh | sh
farol-host setup
Each tmux window there shows up on the phone next to your Mac's sessions. farol-host is still experimental, and the farol-remote roadmap lists what is left. More on farol.sh/remote and in the docs.
| Action | Keys |
|---|---|
| New task | ⇧⌘N |
| New session | ⌘T or ⌘N |
| New worktree session | ⇧⌘T |
| Close pane or session | ⌘W |
| Next or previous session | ⇧⌘] and ⇧⌘[ |
| Go to session 1 to 9 | ⌘1 to ⌘9 |
| Search sessions, files and folders | ⌘P |
| Split right or down | ⌘D and ⇧⌘D |
| Move between panes | ⌘[ and ⌘], or ⌘⌥ with arrows |
| Resize the focused pane | ⌘⌃ with arrows |
| Equal pane sizes | ⌘⌃= |
| Zoom the focused pane | ⇧⌘↩ |
| Name the focused pane | ⇧⌘R |
| Find, next, previous | ⌘F, ⌘G, ⇧⌘G |
| Find the selected text | ⌘E |
| Toggle sidebar | ⌘B |
| Toggle files | ⇧⌘E |
| Save the open file | ⌘S |
| Review changes | ⌥⌘R |
| Commit | ⌥⌘C |
| Push | ⌥⌘P |
| Git graph | ⌥⌘G |
| Resolve conflicts | ⌥⌘M |
| Next or previous change to decide | ⌥↓ and ⌥↑ |
| Mark the file resolved | ⌘S |
| Accept yours or the incoming side, for the whole file | ⌃⌘← and ⌃⌘→ |
| Continue once every file is resolved | ⌘↩ |
| Back to the terminal from the conflicts | Esc |
| Full screen | ⌃⌘F |
| Settings | ⌘, |
| Reload configuration | ⇧⌘, |
| Farol Inspector: what Farol spends, to tell if it's slowing down | ⌥⌘I |
Closing the last session quits Farol. Closing a session or quitting asks first when a program is still running. Ghostty's other default shortcuts, such as ⌘K to clear and ⌘+ to zoom, work as in Ghostty.
Use the settings page (⌘,) or edit ~/.config/farol/config directly. They are the same thing: the page writes to that file, and saving the file updates the page and every session right away. Settings → Terminal has a button that opens it.
theme = Catppuccin Mocha
font-family = JetBrains Mono
font-size = 14
The file uses Ghostty's format, so every terminal option in the Ghostty docs works: fonts, colors, cursor, padding, keybinds. Options for Ghostty's own window, tabs and quick terminal do nothing, since Farol draws its own window. If you also use Ghostty, your Ghostty config loads first and Farol's file wins.
When you save a key or a value Farol cannot read, it tells you which one, and Settings → Terminal keeps the list until it is fixed.
Drop a theme file in ~/.config/farol/themes and it shows up in Settings → Appearance, right after Farol's own. Settings has a Themes folder button that opens it. A theme file is a few lines of Ghostty config:
background = #1e1e2e
foreground = #cdd6f4
cursor-color = #f5e0dc
palette = 0=#45475a
palette = 1=#f38ba8
You need macOS 14 or later and Xcode 26.
make install # builds Farol into /Applications
The first build downloads a prebuilt libghostty for the pinned Ghostty commit. After that, make run gives you a quick debug build and make help lists everything else. Farol Dev, the make run build, is signed only locally, so macOS doesn't show its notifications. Status dots and the Dock badge still work.
To compile libghostty yourself, install Zig 0.16 and the Metal toolchain (xcodebuild -downloadComponent MetalToolchain), then run FROM_SOURCE=1 ./scripts/build-ghostty.sh.
Sources/
GhosttyTerminal/ the only code that touches Ghostty's C API
FarolCore/ git and worktree logic, no UI, tested
Farol/ the app: sessions, sidebar, window, settings
Tests/ FarolCore tests, run with `make test`
scripts/
build-ghostty.sh fetches or builds libghostty for a pinned commit
bundle.sh assembles build/Farol.app
make-icon.swift builds the app icons from assets/icons/source
check-style.py keeps comments and docs plain
Farol embeds Ghostty through its internal API (ghostty.h). Ghostty makes no stability promise for it, so the commit is pinned in scripts/build-ghostty.sh. Keeping every call in GhosttyTerminal means an upgrade only touches that folder.
Issues and pull requests are welcome. See CONTRIBUTING.md to get started.
MIT. Farol includes libghostty, which is also MIT licensed. See THIRD_PARTY_NOTICES.