A tab holds a tree
Vertical and horizontal splits nest, so any mix is just what happens when you press the second key. Closing a pane collapses its parent and gives the space back to its sibling.
You do not run one command and watch it any more. You run four agents in four panes, and the real work is reading what they changed. That part happens here, without leaving the terminal.
A working mock, running in this page. Nest a couple of splits, rotate the tab bar, click a file to watch it take over the tab, right-click a pane, double-click a tab to rename it. It behaves the way the real app behaves.
Vertical and horizontal splits nest, so any mix is just what happens when you press the second key. Closing a pane collapses its parent and gives the space back to its sibling.
Top, side, or hidden, cycled with one key. Long branch names fit down the side of a wide display where they never fit across the top of one.
Branch, ahead count, merge conflicts, staged and changed, with stage, unstage and discard on each row. Diffs render unified or side by side. It runs your git.
A folder of repositories is what a working directory usually is, so each one in the tree says which branch it is on and how many files it has touched. Click the number and its changed files become a tab of the sidebar: read a diff, stage it, commit what you accepted.
Click a branch to filter, switch, create, or check out a remote one as a local branch that tracks it. Click any branch to see what it has that yours does not, file by file. Merging and pushing stay in the pane below, where their output can be read.
Click a file and you get three columns: tree, file, terminal. Escape puts your exact layout back. Syntax highlighting across 30 languages, and files are editable.
A dot per pane and per tab, driven by real shell marks rather than guesswork: running, finished, failed. A dot appears only when a pane knows something you do not.
A long command that finished in a pane you were not watching. Notifying about anything already on your screen is how a notification becomes noise, and noise gets muted.
Settings checks a static file, and the same button downloads the disk image, replaces the app and relaunches. One GET, no identifiers, and it is listed in the table below.
Drag a pane to an edge to move it there, to the middle to swap, to a tab to send it. Your tabs, splits and directories are restored on the next launch, and a moved pane keeps the shell it was already running.
⌘W names what it is about to close, the pane, the tab, or Marlin, and lists what is still running in it. Return answers the focused button, which is Cancel. ⌘+ and ⌘- scale the whole interface, terminals included, and the level is a line in your config file.
Files open ready to edit, highlighted while you type, with a gutter and ⌘S. A write is refused if the file changed on disk since you opened it.
| ⌘D · ⌘⇧D | Split vertically, split horizontally |
|---|---|
| ⌘W | Close the pane, or the tab when it was its last, asking first |
| ⌘T · ⌘1–9 | New tab, go to tab |
| ⌃⇥ · ⌘] · ⌘[ | Cycle tabs |
| ⌘⇧A | Focus the next pane that wants you: running, waiting or failed |
| ⌘⇧P · ⌘P · ⌘⇧F | Command palette, go to file, search every file |
| ⌘B · ⌘⇧B | Toggle the sidebar, cycle the tab bar |
| F2 · ⇧F2 | Rename the pane, rename the tab |
| ⌘⇧↩ | Zoom one pane to the whole tab |
| ⌘F · ⌘K | Find in scrollback, clear the buffer |
| ⌘+ · ⌘- · ⌘0 | Scale the whole interface, and back to 100% |
| ⌘E · ⌘S · Esc | Edit the open file, save it, put your panes back |
| ⌘, · ⌘/ | Settings, this table |
Every one of these is a good program, and three of them are faster at being a terminal than Marlin is today. The question is what happens after the agent stops printing.
| Instead of | What you would miss here | What you would give up |
|---|---|---|
| iTerm2 | Reading the change: git status, diffs and staging in the window, and a per-pane dot that says which agent needs you. | Fifteen years of features. Profiles, triggers, tmux integration, shell autocomplete: Marlin has none of them. |
| Ghostty or Alacritty | The same. They are terminal emulators, deliberately, and the review half of the loop happens in another window. | Speed that has been measured. Marlin's renderer is unmeasured and this page will not claim otherwise. |
| Warp | Nothing you would notice at first; Warp is the closest thing to this idea. | An account, a cloud, AI in the loop and closed source. Marlin has no backend at all, and the trade runs the other way: no team features, no shared blocks. |
| The terminal in your editor | Panes that are a first-class layout rather than a drawer, and a review view that does not fight the editor for the window. | Your extensions, your language servers, your debugger. Marlin's editor is a textarea and says so. |
A terminal sees everything: your keys, your tokens, your employer's source. That makes the interesting question not what a terminal can do, but what it refuses to do. Marlin's answers are deliberate, and they are checkable, because the source is right there.
| Promise | What that actually means |
|---|---|
| No cloud, no account, no sign-in | Marlin has no backend. There is nothing to log into, and no service that could be down or shut off. |
| No telemetry, of any kind | Not opt-out, not anonymised, not “just crash counts”. There is no analytics code in the app. |
| Your terminal output is never stored or sent | Scrollback lives in memory in your session, and the diagnostics log deliberately contains none of it, because scrollback holds passwords and private source. |
| Error reports are a button, not a background job | Problems are written to a local file. Reporting opens a GitHub issue pre-filled and editable, so you read exactly what you are sending before you send it. |
| No AI in the loop | Marlin does not read your commands, does not send them anywhere, and has no model in it. It runs agents; it is not one. |
| Your shell stays your shell | Marlin never parses your command line. Completion, history and autosuggestions remain fish’s and zsh’s job, so your history file is never duplicated somewhere new. |
| Your git is your git | The sidebar shells out to the git binary you already have, with your config, your credentials and your hooks. No second implementation with its own idea of what your repository contains. |
| The webview is locked down | It can receive terminal output and move its own window. No filesystem, shell or network permission, and a content security policy that blocks outbound requests, so nothing the terminal prints can call home. |
| Links open in your browser, and only http and https | A UI that can be navigated by something the terminal printed is a class of problem worth closing before it exists. |
| Settings are a file you own | ~/.config/marlin/marlin.toml, plain text, editable by hand, safe to keep in a dotfiles repo. |
| The update check is the only thing that goes out | At most one GET a day to a static version.json, carrying no identifiers and no version query string, plus the disk image itself if you ask for it. Those are the only outbound requests Marlin makes, and there is no signature to verify on what comes back: TLS to the two hosts is the whole of what it trusts. |
One honest caveat. Builds are not yet signed with an Apple Developer ID, so macOS will warn you about an unidentified developer. The instructions below say how to get past it. The 0.1.1 disk image on the releases page today is a worse case than that, and needs one extra command before macOS will let you make that decision at all; the download section explains it. Building from source avoids the question entirely, and is the option we would pick if we were you.
spctl with exit 1,
re-checked 29 August. The download section says what to do about that. Notarising it needs an
Apple account this project does not have.Eleven items that used to be on this list have been built and were taken off it, most recently session restore, panes you can move, and the measurement harness that rewrote the first two lines above. A stale gap list flatters you exactly as much as no gap list at all.
What exists to download today is one file: an aarch64 disk
image on the releases page, version
0.1.1, built by hand on one Apple silicon Mac. If you are on an Intel Mac, on Windows, or on
Linux, the honest answer right now is to build it from source, which takes one command and is
written out below.
| You are on | Today | In 0.1.2 |
|---|---|---|
| macOS, Apple silicon | The aarch64 disk image on the releases page. |
One universal image, both architectures in the same file. |
| macOS, Intel | Build from source. | The same universal image. |
| Windows 10 or 11, x64 | Build from source. | A -setup.exe that installs for the current user with no admin prompt, and
an .msi for anyone deploying it. |
| Linux, x64 | Build from source. | A .deb for Debian and Ubuntu, and an .AppImage for everything
else. Neither is signed and neither needs to be: nothing on Linux asks. The AppImage is
around 78MB against a 6MB disk image, because it carries the webview with it. |
Nothing is signed by a certificate authority. There is no Apple Developer ID and no Windows signing certificate, so macOS and Windows both stop you the first time. That warning is accurate: nobody has paid anyone to vouch for this build. It happens once. Once you have allowed it, macOS saves the exception and Marlin opens by double-clicking from then on, and updates install in place without ever asking again.
Read this before you download the 0.1.1 disk
image, because the paragraph after it does not apply to that file. The one artefact on the
releases page today, Marlin_0.1.1_aarch64.dmg, was built by hand on 16 August before
the signing bug below was found. It is not merely unsigned, it is malformed: the
binary carries the linker's ad-hoc signature, which seals no bundle resources, and macOS reads a
signature that promises resources it cannot find as corruption. So you do not get the
unidentified-developer warning. You get “Marlin is damaged and can’t be opened. You
should move it to the Trash”, whose two buttons are Done and Move to Trash.
The Open Anyway route described further down does not apply to it: that exists for
the unidentified-developer rejection, and this bundle is refused one step earlier, before trust is
the question.
Two things that are widely recommended for this dialog do not
work here, and we measured that rather than assuming it. All three rows below are
spctl -a -vvv -t exec against the published 0.1.1 bundle, run 29 August 2026. Exit 1
is a malformed bundle. Exit 3 is the ordinary unidentified developer, which is the one you can
click through.
| The disk image as downloaded | exit 1 · code has no resources but signature indicates they must be present |
|---|---|
After xattr -dr com.apple.quarantine | exit 1 · unchanged. The quarantine flag was never the problem, so removing it changes nothing. |
After codesign --force --deep --sign - /Applications/Marlin.app | exit 3 · rejected. Now it is the normal unidentified-developer case, and Open Anyway appears. |
So if you want to run 0.1.1 today: drag it to
Applications, run codesign --force --deep --sign - /Applications/Marlin.app, then
follow the macOS paragraph below as normal. That re-seals the bundle on your own machine with the
same ad-hoc signature the build should have carried. It vouches for nothing and it is not a
substitute for notarisation; it only turns an unopenable file into one you can consciously decide
to trust. If you would rather not run that command on a stranger’s binary, and that
is a reasonable position, build from source or wait for 0.1.2.
Fixed at source on 22 August, in the build rather than in this
advice. "signingIdentity": "-" in
tauri.conf.json makes tauri-bundler actually run codesign
instead of skipping the step, and a release job now fails the build if Sealed Resources ever
goes missing again. Neither has reached a download yet, because no tag has been
through the pipeline since. 0.1.2 is the first build that will carry it.
On macOS, from 0.1.2 onwards, you get a dialog headed “Marlin”
Not Opened, saying Apple could not verify Marlin is free of malware. Its two buttons are
Done and Move to Trash, and Move to Trash is the highlighted one, so do not press it
out of habit. Open System Settings, then Privacy & Security, scroll to
Security, and click Open Anyway next to Marlin. That button is only there for about
an hour after you tried to open it. Right-clicking and choosing Open used to do the same job and
stopped working in macOS Sequoia. If you would rather stay in a terminal,
xattr -dr com.apple.quarantine /Applications/Marlin.app does it, and downloading with
curl rather than a browser skips the question entirely, because the flag that
triggers it is attached by whatever downloaded the file. All of that is about a
well-formed unsigned bundle, which is what 0.1.2 will be and what 0.1.1 is not.
On Windows, SmartScreen shows “Windows protected your PC”. Choose More info, then Run anyway. On Linux there is none of this: nothing asks for a signature and neither package raises a warning of any kind.
Take the .deb if apt is an option, which is why it is listed
first. It declares its dependencies, so apt resolves them for you. The AppImage carries the webview
but not the whole desktop, and CI has twice caught it wanting a library a bare system did not have,
libfontconfig.so.1 and then libfribidi.so.0. Both are ordinary parts of a
GTK desktop and you almost certainly have them, but nobody has yet run either package on a
real Linux desktop, so that is not a claim being made here. If the AppImage exits
complaining about a missing shared library, take the .deb.
What the AppImage does not need is FUSE 2, whatever the usual AppImage advice
says. Its runtime is static-pie linked, which file and ldd
both confirm on every build, so it cannot be missing libfuse.so.2 and installing
libfuse2 is not a step you need.
--appimage-extract-and-run works if you would rather not mount anything at all.
This paragraph said the opposite until 23 August 2026. It described
classic AppImage behaviour that this AppImage does not have, and it was written a few hours before
the smoke test existed to check it.
The Windows build is a pipeline, not yet a fact. The release workflow that produces those installers, and that installs the Windows one on a runner to check it starts, has run and gone green on all three platforms, most recently on 22 August 2026. What it has never done is run against a tag, which is what version 0.1.2 will be. And that check proves the installer works and the process starts, not that the window renders or that typing does: nobody has sat in front of Marlin on Windows, and nobody has run the Linux packages at all. Say so in an issue if it misbehaves; it is a bug, not a known limitation.
$ git clone https://github.com/ehsangazar/marlin $ cd marlin $ pnpm install $ pnpm tauri build
Needs Node, pnpm and a stable Rust toolchain.
The app lands in src-tauri/target/release/bundle, and building it yourself avoids
the signing warnings entirely.
# zsh, from the installed app $ echo 'source "/Applications/Marlin.app/Contents\ /Resources/shell/marlin.zsh"' >> ~/.zshrc # or from a clone, run in the repository $ echo "source $PWD/shell/marlin.zsh" >> ~/.zshrc
Optional, and the hooks ship inside the app so there is nothing to download. It emits standard prompt marks so the status dots and directory tracking work. Without it you get a working terminal with no dots, which is degraded rather than broken.
Open an issue with what you did and what happened. Agents triage every issue, reproduce what they can, and open a pull request where the fix is obvious. A human reviews everything before it ships, so nothing lands unread.
Marlin is free and always will be. Sponsorship is monthly and cancellable on purpose, because this is an early project and you should be able to stop as easily as you started.