A mouse-and-menu-driven TUI file manager for your POSIX-compliant terminal, built around a real embedded bash shell — written in Go.
breakthrough puts classic GUI file-manager idioms — a browsable panel,
context menus, floating dialogs, clickable breadcrumbs — over a real bash
session, without ever leaving the shell. It's built with
tcell and
tview; it is not integrated into Bash
itself — no fork, no patch, no hook into Bash's own internals. Its console
is a first-class citizen all the same: its own history, path completion
against the current directory, multi-line scripting, and every command
running through your actual $SHELL, started the same way an
interactive login shell would be — so it sources that shell's own
startup files and expands its aliases, whichever shell that actually is
— not a reimplementation of one.
It is explicitly not an attempt to rebuild Midnight Commander — the goal is its own UX philosophy, closer to classic GUI file managers, just in the terminal.
-
Panel-based directory browsing: arrow-key and mouse navigation, a clickable path breadcrumb bar with Start/Root/Home/Back/Forward — its history covers a trip into search results or the trash exactly the same as a real directory, and Back/Forward into any of them restores the cursor row it was left on, a search's own results included, shown again exactly as they were rather than a live re-run — plus Reload (
⭯, right before the path itself), re-reading the current directory straight from disk for anything this app has no other way to notice on its own: another process changing files underneath it, a network/mounted filesystem's own content changing, and so on — sortable Name/Size/Modified columns, and file-type indicators (directory, symlink — including broken and multi-hop chains, socket, FIFO, device, mount point, hard link) matching Midnight Commander's own glyph scheme. Every entry Enter can navigate into — a directory, a symlink to one, a mount point, or..itself — gets its own name highlighted dark yellow (just the name, not the trailing/or a symlink's-> targetarrow), so folders stand out from plain files at a glance. Beyond that, a name's own text color also tells them apart: dark green for executable, red for a broken symlink, a darker red for anything the current user can't actually read (checked with a real permission check, not just Mode's bits — a/procentry included), cyan for a working symlink to a file, orange for a socket/FIFO/device, magenta for a recognized archive extension, and a dim gray for a dotfile/ dotdir (unless one of the other cases above already applies). -
Columns that never lie: Size and Modified take exactly the width their content needs — six columns for human-readable sizes, ten for a Unix timestamp, nineteen for a full date — and the name column absorbs the difference. Switching either format reflows them immediately, and the column header is simply "mtime", short enough never to constrain the column. When something has to give it is the filename, shortened in the middle (
annual-re…final.pdf) so both the identifying start and the extension survive. This matters most in split view, where each pane is half as wide: before, Size collapsed to…and Modified disappeared off the right edge entirely. -
A live filter, tucked behind a compact "Ÿ" button in the top row (an "Nx" count appears right before it once one or more are actually narrowing the listing — glowing the same slow green breathing pulse the header's own "@" connection button uses (see below), or turning solid red instead if one is currently hiding everything a directory would otherwise show): click it, or press
/, to open a small dropdown with three independently combinable rows, each narrowing the listing live as you type. A compact "f" button sits right next to it (one column of breathing room between the two) — click it, or pressf, to open Find instead: a separate, draggable dialog with its own close button, for locating a file by name or content anywhere under a chosen starting directory, byfindorlocate.- Glob/regex — the original filter, with a Glob/Regex toggle for how the pattern is interpreted and its own checkbox to switch it off without losing what's typed.
- Size — comparison expressions like
> 1m,>= 500k, or a range by joining two withand(> 1m and < 1g). Units areb/k/m/g/t, binary (1024-based), the same convention the Size column's own human-readable mode already uses; a bare number means plain bytes. - Modified time —
before/after/between ... and ..., each side either an absolute date (2026-09-01, optionally with a time) or a relative one (7 days,2 hours ago); a bare relative expression on its own (last 7 days) means "modified within that span". - Labels — ten small toggles, one per color label (
0"no label" through9), each shown as its own colored swatch with no number or text alongside it — id 0 shown in the dropdown's own plain background rather than a color, since "no label" has none of its own. Toggling more than one ORs them together (label 2 or 3, say, shows both at once). Left/Right move between the ten swatches while this row has focus; the one the keyboard is currently on is marked<>around it, its own color always staying exactly what it already was — never swapped out just because the keyboard happens to be on it.
A sixth row, Exclude dirs, sits below all four: not a filter of its own, but a single on/off switch that applies to all four at once — while on, a directory is never hidden by any of them, however it would otherwise have matched (or failed to match); only plain files are ever actually filtered.
Typing into any field auto-activates its own row, the same way it already did for glob.
Tab/Shift+Tabcycle through all nine of the dropdown's own pieces (checkbox + field for size and modified-time, checkbox + mode button + field for glob, the Labels row, plus the "Exclude dirs" checkbox),Space/Entertoggles whichever checkbox (or, on the Labels row, whichever swatch) has focus,/— once the dropdown is already open — jumps straight to the next of the three expression fields instead (the same "press it again to advance further" trickCtrl+Talready does for the tab switcher; safe to repurpose since a bare filename can never contain/in the first place; the Labels row has no field of its own, so/from it jumps straight past to whichever field comes next), andEscapecloses it from any of them. Carries over across a directory change by default (filter_persistent), so browsing a whole tree with the same filter switched on is the normal way to use it, not a special case; set it tofalseto go back to every new directory starting unfiltered instead. -
The seven nav buttons (Start/Root/Home/Up/Back/Forward/Reload) at the very start of the path bar are real, highlighted buttons now — a plain background-colored square either side of each glyph, with its own separator column between one button and the next. Root (
/) sits right next to Start and jumps straight to the filesystem root — the same destination the path breadcrumb's own leading "/" already links to, just as a second, consistently styled, easier-to-spot target for it. -
Tabs: several directories open at once in one window, each keeping its own history, filter, sort order, selection and cursor position, so switching away and back leaves everything exactly as it was.
^1through^0(orAlt+1...Alt+0— bound to the same tabs, for terminals that can't report one or the other) jump straight to a tab,^Tab/^Shift+Tabstep through a switcher showing every tab's full path.t/^Topens the same switcher without moving, and works everywhere the others might not. A compact numbered strip sits beside the filter — numbers rather than paths, so the header row's width stays put as you navigate — and shrinks to just a "+" while only one tab is open. The layout is saved on exit, but a barebreakthroughopens in the current directory rather than reopening it — setrestore_tabs = trueto resume your last layout instead. A directory named on the command line always wins regardless of this setting. -
Split view (
s): two of those tabs on screen at once, side by side (with a plain background-colored gap column between them) or stacked (zthenoflips it, or setsplit_stackedonce and forget it). With a single tab open,sopens a second one on the same directory — the usual starting point for copying between two places in one tree; otherwise it pairs you with the tab you last split with. To choose the other pane yourself, the tab list (t) gives every row a◫button, including its "+ New tab" row, which creates a tab and splits with it in one step.Tab, or a click, moves between the panes, and the one you are in keeps the keyboard, the context menu and every shortcut; the highlighted row shows which that is. Switching tabs while split replaces only the pane you're in — the panes never swap sides. The split is saved and reopened alongside the tabs. See docs/user-guide.md for the details. -
A primary keyboard layer of plain letters —
c/x/vcopy/cut/paste,d/DTrash/remove permanently,rrename,i/IProperties/ Details,s/Ssplit/swap panes, and more — active only while the file listing itself has keyboard focus, never while typing in the filter box, the path editor, or the command line. No modifier key means no terminal/layout/multiplexer to trip over — the same approach ranger, nnn, lf and vifm all settled on. Related, rarer actions sit behind a chord (a letter, then one more within about four seconds by default — "Chord timeout (ms)" under Options, Behavior's own Miscellaneous subsection — with a countdown in the status bar and a clickable legend in the button bar):gto jump somewhere (ggtop,ghhome,gr/,gbTrashbin),pfor permissions (pmchmod,pochown),zfor display toggles (zssize format,zttime format,ziNerd Font icons,zllabel — set/clear one of 9 colors on the focused or checked row(s),zosplit orientation,zwswap panes),ofor Options (oothe screen itself,ommouse reporting on/off — quick, direct toggles without opening the screen at all). See docs/user-guide.md. -
No function keys anywhere in the application, and — as of the latest round — no Ctrl-letter bindings either, apart from the two things a plain letter genuinely cannot do (quit/cancel from literally anywhere, even mid-edit) and one narrow extra reach for the tab switcher (
^Talongsidet, for walking to the next tab while the switcher itself is already open). Mouse reporting's own former^_— a single, narrow toggle that fired even with a dialog open or a text field focused, since a Ctrl combination safely can where a plain letter never can — was deliberately retired anyway in favor ofomabove, a considered trade-off rather than an oversight. -
A context menu on
mm(themchord's own doubled prefix, the same "gg"/"oo" shape every other family uses for its main destination) or right-click, showing only what actually applies right now rather than a fixed list of everything it can ever do: Look, Edit, Execute, Open with… (all three of the latter dropped for a directory), Rename, Copy/Cut/Multiply, Paste (only once the clipboard has something in it), Move to Trash, Properties (editable — name, permissions, click a bit or type the octal value directly, owner and group via a scrollable picker of every local user/group, modified date and time), plus four▸submenus that replace the list in place when chosen (Windows Explorer's own cascading-menu idea, without needing room to open beside it): "Label" (set/clear one of 9 colors — seezlabove — each shown as a small colored swatch next to its name), "More actions" (New file, New dir,tail -f, chown, chmod, Sed Replace, Batch rename, Undo last rename, Compare, Rsync, Compress…, Extract/Extract-delete-original (the latter two only for a recognized archive), Remove, Paste following symlinks), "Selection" (Select all/Deselect all/glob-pattern Select +/-), and "Tabs & Split" (New/close tab, Switch tab..., Split on/off, orientation and Swap panes — the last two only once a split actually exists). Right arrow drills into a submenu with no Enter needed;◂ Back,Escape, or Left arrow step back out one level at a time —◂ Backis also a clickable button in the status line itself whenever a submenu is open, not just a row inside the list. Browsing the Trash itself replaces the whole menu with just Restore/Empty Trash/Properties, since almost nothing else still applies there. Once open,l/e/E/r/c/x/d/i— the same letters Look/Edit/Execute/ Rename/Copy/Cut/Move to Trash/Properties already have on their own — fire that entry directly, without arrowing down to it first; one whose own entry isn't currently showing does nothing.o/t/Ado too, for Open with…,tail -f, and Deselect all — all three also directly reachable asmw/mt/mAfrom plain browsing (notmo: the plain-letter layer's own key for Open with… isW, since lowercaseois already theochord's own prefix), without opening the menu at all.magain (mmmfrom plain browsing) fires Multiply, the one entry with no keyboard route of its own anywhere else, since its own natural letter is already this chord's own prefix key.mf/mdreach New file/New dir directly from plain browsing too, without opening the menu at all — both create their new entry directly inside the active panel's own current directory (remote-aware the same way Rename already is), prompting only for a name. Every entry one level into a submenu has its own mnemonic too, each reusing whichever letter/case that same action already answers to elsewhere (chown/chmod reuse thepchord's ownpo/pm, Compress/Extract thejchord's ownjc/je/jE, and so on) — the status line's own legend always lists the currently open submenu's own entries, the same way it lists the top level's. -
Copy/Cut/Paste (
c/x/v, or the context menu): works on the whole current selection, not just one file. Pasting into the very directory a file is already in, or a directory into one of its own subdirectories, is refused outright rather than started at all — the first would have destroyed the only copy there ever was, the second would recurse into itself without any bound. Paste runs in the background — a file that already exists at the destination opens a small dialog (Overwrite, Skip, "Merge into existing folder", an "all" variant of each for the rest of this Paste, or apply "only if the source is newer"/"only if the source isn't empty" to every conflict it still runs into) without blocking anything else in the same Paste: whatever doesn't conflict keeps copying/moving while that dialog is up, and a second conflict found before the first is answered queues behind it — shown as "(N more waiting)" right in the dialog's own message — rather than stacking a second dialog on top. Unlike every other dialog here, clicking outside it does nothing — one of its own options, or Escape, is the only way past a conflict, so a stray click can never leave one stranded, half-answered forever. Overwriting a directory replaces it entirely (nothing left over from whatever was there before — the right choice when "overwrite" needs to mean "make this identical to the source", not "patch it"); Merge is the explicit alternative, keeping whatever the source doesn't also have. "Auto-merge directories" under Options (off by default, set separately for Copy and Move) skips this dialog entirely for a directory-vs-directory conflict and merges right away every time — a file-vs-file or file-vs-directory conflict always still asks, since merging has no meaning there. Starting a further Paste while one is already running queues it rather than running it alongside the first or replacing it outright — shown as "(+N queued)" right in the status bar's own progress line — and it starts automatically the moment the one ahead of it finishes, in the order each was asked for. Ctrl+C pauses a running Paste and asks what to do next — continue, cancel just this job and let whatever's queued run, or cancel everything; whatever item was already mid-transfer finishes normally for a Copy, or, for a Cut, stops immediately with its own source left untouched, but only if you actually choose to cancel. A backgrounded Rsync or Compress/Extract is cancelled separately: Ctrl+C with no Paste running asks to cancel both together (dropping both their own queues), while the "mR"/"mC" chords and a clickable "✕" on each progress segment reach each one individually, "current job only" or "current plus its own queue" apart from the other. Any real failure (permission, a full disk, ...) is collected rather than stopping at the first one, and reported once the whole Paste is done. Every open tab showing the destination reloads automatically as items actually land, not just once the whole Paste is fully done — including while an unrelated conflict's own dialog is still sitting open, since whatever doesn't conflict keeps landing regardless; Cut also reloads every open tab showing one of the moved items' own source directories, live, as items actually leave, so a tab something was cut from never keeps listing a file that's actually gone — Copy leaves its own source list alone, since nothing there was ever removed. Whatever's currently on the clipboard shows two ways: every row it holds gets a full-row tint — a slightly bluish-tinted grey for Copy, a slightly pinkish-tinted grey for Cut, a deliberately matched pair rather than two shades of one plain grey, so a Cut selection reads as visually different from a Copy one at a glance rather than needing a brightness comparison. This tint stays visible even on whichever row the cursor happens to land on: the ordinary focus highlight wins outright over it while this panel actually has keyboard focus, so the cursor's own position among several tinted rows is never ambiguous, but a dimmer variant of the same tint takes over once focus moves elsewhere, still clearly Cut- or Copy-colored rather than fading to a plain, indistinct gray — across every open tab showing that row, not just the one Copy/Cut was pressed in; and the status bar names it — "Copy: 3 files, 1 dir" or "Cut: ..." — right after the button-bar chord countdown's own spot, for as long as there's something to Paste. Once a Paste actually starts, that same spot switches to its own live progress instead — a spinner, "Copying"/"Moving" and how many of the selection's own top-level items are done, and a two-row progress bar packed into one line of half-block characters (the top half is that same item-count fraction, the bottom half is the file currently being written's own byte progress), plus whichever real file is being written right now (its bare name, e.g. inside a large directory this Paste is still working through). A one-time background scan of the whole selection's byte size (started alongside the Paste itself, never blocking it) adds two more things once it's done: a single character before the bar showing what percentage of the total bytes has copied so far (the same shrinking/filling block style the chord countdown uses), and an estimated remaining duration after the bar. A same-filesystem move is atomic regardless of size, so a Cut within one filesystem usually finishes too fast for any of this to show anything at all — expected, not a bug: there's nothing to report progress on. Plainvfollows whichever "Follow symlinks" default is set under Options for Copy or Move (off by default, set separately for each): off recreates a symlink as a symlink at the destination, on replaces it — and any symlink nested inside a pasted folder — with a real, independent copy of whatever it points to (recursively, through a multi-hop chain too), instead.V(Shift+Paste, or the context menu's "Paste, following symlinks") flips that default for this one paste only, without changing the setting itself. Works for both a Copy- and a Cut-marked clipboard alike; for a Cut, only the original link itself is removed afterward — never whatever it pointed to, however far away that actually lives (a different filesystem, a network mount).Vonly asks for confirmation first when it's actually the thing turning dereferencing on (the configured default was off) — dereferencing can turn a small, instant symlink into an arbitrarily large copy, so activating it is never a single, undialogued keypress; flipping it back off for one paste needs no such confirmation, since that's the safer direction. Two more Copy/Move settings round this out, both under Options and set separately for Copy and Move: "Preserve attributes" (on by default) carries the source's own permissions, ownership, and modification time over to the destination — turning it off leaves the destination at whatever creating it just produced. "Stable symlinks" (off by default, matching Midnight Commander's own default for the equivalent option) rewrites a copied symlink's target to point at its new location if that target lives inside the tree being copied, rather than keeping the original target verbatim — without it, such a link can end up pointing back at the original source (an absolute target) or nowhere at all (a relative target that climbed out of the copied root and back in by its old name) once that source is later moved, renamed, or removed. -
Move to Trash / Remove:
dor Entf moves the current selection to your own trash — recursively for a directory, no confirmation by default, since that's the reversible action by design ("Confirm before moving to Trash" under Options, off by default, asks first anyway for anyone who wants that extra safety net).D, Ctrl+Entf (best-effort — terminal-dependent;Dis always the reliable one), or the context menu's "Remove" permanently deletes instead (a file likerm, a directory recursively likerm -rf, empty or not), always behind a confirmation dialog with Cancel preselected — a single stray keypress can never confirm it by itself. "Go to Trash" (thegchord's owngb) jumps straight into it without needing to know its path; "Restore from Trash" (r, while browsing it) and "Empty Trash" (D, same confirmation) round it out. Restoring something whose original path now has an unrelated file sitting on it — recreated after the original was trashed, say — opens the exact same conflict dialog a Paste collision already does (Overwrite/Skip and their "for all" and "if newer"/"if not empty" variants, Skip preselected as the safe default), rather than silently overwriting it or refusing outright with nothing but an error; a multi-item restore can pull items whose own original locations were entirely different folders, each resolved independently. Persistent by default — lives under~/.local/share/breakthrough/trash, so it's still there tomorrow, even across a login session boundary — or session-scoped viatrash_persistent = falsein your config, under$XDG_RUNTIME_DIRinstead, gone once the session ends; running as root (e.g. viasudo) always gets the persistent path regardless, since root has no real session of its own for a session-scoped trash to mean anything for. Kept from growing forever by two settings checked once at startup, not on every single trash operation:trash_max_age_days(30 by default — anything older is removed unconditionally) andtrash_quota_percent(10 by default — a backstop, oldest item first, only if age alone didn't already bring the trash back under that share of the filesystem it lives on); either one is0to disable it. Anything actually removed this way is reported once, on the next start. Browsing the trash itself ("Go to Trash") shows each item's own original path in place of its real on-disk name (a collision- avoidance hash you'd otherwise have to squint past — two files trashed from the very same location, more than once, still stay distinguishable this way) and labels the Modified column "Deletion time" instead — both, like the Modified column always has, respecting the Options overlay's timestamp-vs-formatted toggle and the column's own sort. -
Multiply (
mm, or the context menu): creates one or more copies of the selection right beside it — a directory works just as well as a file, since it's an ordinary Copy underneath, never a Move. A guided dialog, not a pile of every option shown at once: a Strategy dropdown picks Numbered (default, counts up from 1 until a free name is found), Fixed suffix text (the same literal text every time — duplicating the result again doubles it,report.txt_copy_copy, rather than retrying automatically), or Date/time — computed once, with its own second dropdown ("Date/time format type") picking Go format string (default), Strftime-style Format, or Unix timestamp; the format field below it holds each syntax's own independently- edited example text (switching back and forth never loses either one), or — once Unix timestamp is picked — is disabled and shows today's real timestamp instead. Only the current strategy's own fields ever appear at once, never every strategy's combined. The suffix always lands after the original name's own entire text, extension included:archive.tar.gzduplicates toarchive.tar.gz_1, neverarchive.tar_1.gzorarchive_1.tar.gz— nothing about a basename is treated as more "real" than the rest of it just because it follows a dot. A live Preview line shows the exact name the first duplicate would get right now, given every field's current value; Cancel and Duplicate sit bottom-left/bottom-right, the same as every other dialog's own confirm/cancel pair in this app. Whatever's chosen becomes the new default shown next time (Options → Behavior → Duplicate) — the one setting group in this whole app that adapts itself this way — but only once "Duplicate" is actually pressed; Cancel never touches the sticky default. -
Execute (
E, or the context menu, files only): runs the current entry directly, full screen — the same path typing its path into the command line already takes, just without having to type it out. Does nothing for a directory; the real shell reports "Permission denied" for a file that isn't actually marked executable. Not supported yet for a remote connection. -
Open with… (
W, or the context menu, files only): runs any program you type against the selected file instead of always the configured editor — prefilled with whatever you typed last time, handed to your real shell exactly as typed (so a multi-word command with its own flags, likelibreoffice --writerorcode -w, works the same as at a real shell prompt). Works for a remote file exactly like Edit already does: downloads a local temp copy, runs the typed command against it, and uploads the result back over the connection only if it actually changed. -
Sed Replace (
S, or the context menu): runs a realsed(1)substitution against the current selection — one file or several, not a directory tree. A guided Find/Replace pair (Regex, Extended regex-E, Case-insensitive, and Replace-all-per-line toggles) builds the script for you, or drop straight into the advanced field and write the sed script yourself for anything real sed can do — address ranges, multiple commands, backreferences. Always previews first, as a Name/Line/Excerpt table — one row per changed line, skipped files listed with why — computed in the background with a live "Checking N of M" status; only Apply, behind the same Cancel-preselected confirmation, actually writes, optionally keeping a.bakof each original first. Never uses sed's own-i: GNU and BSD/macOS sed take incompatible arguments for it, so this always runs sed as a plain filter and writes the result back itself. -
Batch rename (context menu): renames a whole selection through a fixed pipeline of steps — Search & Replace (literal or regex), Case (UPPER/lower/Title/Sentence), Trim (drop N characters off either end), Template (rebuild the name from
{name},{ext},{counter},{parent}and{date}tokens —{parent}_{date}_{counter}turnsIMG_0042.JPGintoHoliday_2026-03-09_001.JPG), Numbering (a zero-padded counter as prefix or suffix), and Extension (lower/upper/remove/replace) — with the steps listed down the left and the selected one's own settings on the right. A step left alone does nothing; there is no separate on/off switch to also remember. Search & Replace and Case only ever touch the name, never the extension, and Extension only ever touches the extension, so a case transform can't quietly rewrite.JPGbehind your back. The whole selection is previewed live, old name beside new, updated on every keystroke rather than behind a "Preview" button — unchanged rows dimmed, a●beside each step that currently changes something, a help line explaining the selected setting, and any collision (two files landing on the same new name, or a name already taken by something that isn't moving out of the way) shown in red with the reason, right where it would happen. A rename chain (1.txt→2.txtwhile2.txt→3.txt, or an outright swap) is not a collision: the renames run in the order that makes it work, through a temporary name where needed. Case-only renames work on case-insensitive filesystems too, regex replacements accept$1or sed-style\1, and a folder's name is never split at its dot unless you ask. In the preview,Spaceskips a row (it's neither renamed nor numbered),u/darrange the numbering order by hand,n/pandc/Cjump between changes and conflicts; Numbering can also count by name or by modification time, forwards or reversed. A whole pipeline can be saved as a named preset and loaded back later — plain JSON files under~/.config/breakthrough/rename-presets/. Nothing is written until Rename is confirmed, and "Undo last rename" reverses the whole batch afterwards. See docs/user-guide.md for the step reference. -
Compare (
C, or the context menu): "are these two things the same, and if not, what's different" — for two files (mark them, or use split view with the cursor on one in each pane) or two whole directory trees. Two files get size/modified side by side plus an immediate verdict from the same size+time heuristicrsync's own default sync mode uses, an on-demand SHA-256 hash for a certain answer, and a real line-by-line diff — a side-by-side view, each pane independently scrollable (Up/Down/Page Up/Page Down, Tab to switch panes) or linked to scroll together via the view's own toggle button — for text. Two directories get a full screen listing every difference and every one-sided path — a one-sided directory is one row, never descended into, so an old backup folder doesn't turn into thousands of lines — with a key to switch between the quick check and a real hash, and to copy a one-sided item across after asking. See docs/user-guide.md for the details. -
Rsync (
R, or the context menu): a realrsync(1)invocation, built from a small dialog rather than typed by hand — Source/Destination (pre-filled from the current selection or panel, and the split view's own other pane where one's open), Archive/Compress/Delete/Dry run as a toggle list, and — the one choice this dialog makes impossible to get wrong by accident — an explicit "Copy the folder's contents in (not the folder itself)" switch, instead of leaving that distinction to whether a trailing "/" happened to be typed on the source path the way a barersyncinvocation always has. Source/ Destination already know about a tab's own SFTP connection: pre- filled asuser@host:pathinstead of a bare local one when that tab is currently connected, its own port carried through to a real-e 'ssh -p PORT'flag rather than guessed — the same applies to an address typed straight in by hand for a host never connected to via the Connect dialog at all, recognized by rsync's own real disambiguation rule (a colon before the first "/" names a host). Two buttons, "Pick source tab…"/"Pick destination tab…", open a list of every currently open tab as an alternative to typing or trusting that one-shot default — picking one fills the field exactly as if Rsync had been opened fresh from that tab, connection included. Syncing between two remote hosts at once shows a warning line:rsync -e sshhas no server-to-server mode of its own, every byte still relays through this machine over two separate ssh connections, never directly between the two remote ends. A live preview line shows the exact command that would run,--deletecalled out in its own warning color the moment it's turned on, right up until either button runs it for real: "Run" hands it to a realrsyncprocess with the real terminal attached, the same way the embedded bash line already runs anything that benefits from one, so--info=progress2's own live progress line renders correctly; "Run in background" instead keeps breakthrough itself fully usable — Copy/Cut/Paste included, at the same time — while its own live percentage shows in the status bar, queuing behind an already-running background rsync the same way a second Paste already queues behind one still in flight. "Run in background" refuses outright — pointing to "Run" instead — when Source or Destination is still a connection that last authenticated with a typed password: a background run has no attached terminal for ssh's own interactive password prompt to ask on, so silently reusing one there would otherwise hang or fail with a bare, confusing "Permission denied". See docs/user-guide.md for the full picture. -
Network Tools (
jthenn) and Hardware Tools (jthenh): two full-screen, browsable catalogs of built-in commands — Network Tools (Ping, Nmap,ip,route,ss,getent,wget,nslookup,dig, netcat,curl, atail -fLogviewer) and Hardware Tools (lsblk,lsusb,lscpu,lsmem,lsdev,hwinfo,inxi,lsscsi) — real external commands, never reimplemented, the same approach Rsync above already takes. An entry that needs one — a host, a URL, a database key — asks for it in a small field first; every one of them then streams its live output into its own small, draggable tool window, non-modal so the Toolbox screen and the panel underneath both stay usable while it runs. See docs/user-guide.md for the full catalog. -
Mounts (
jthenm): a third full-screen catalog, kept deliberately separate from the Toolbox above — a read-only, live table of every currently mounted filesystem (real storage only, pseudo filesystems likeproc/sysfs/tmpfsleft out), built from the realfindmntcommand. Cross-references each mount's own target against/etc/fstabto mark it "Persistent" (survives a reboot) or not (mounted by hand at some point since), and flags bind mounts, so it doubles as a quick answer to "what will still be here after I reboot this box?" See docs/user-guide.md for the full picture. -
Compress (
jthenc, or the context menu's "Compress…") and Extract (jthene/E, or the context menu's "Extract"/"Extract, delete original"): real archive creation and unpacking, through a real external tool (zip, tar, gzip, bzip2, xz, zstd, or 7z) for Compress, plus RAR (via the real, freewareunrar) for Extract only — creating a.rarneeds the proprietaryrarbinary instead, which this app doesn't depend on, so it never appears as a Compress format — never a reimplementation either way. Compress archives the current selection — a file, several files, or a whole directory — into a new file right beside it, or, once a split is active, straight into the other pane's own directory instead; pick a Format, type an Output name, a live Preview line shows exactly what will be created. Extract unpacks the archive under the cursor in one step, without first entering it — into its own directory, or, once a split is active, the other pane's own current directory, the same default Rsync/Compare already use. Either direction's own "other pane" may be a remote SFTP connection too: the real archive tool still only ever runs locally, but the result is compressed/extracted into a local staging copy first and then uploaded, transparently, with its own "Uploading" status while that happens.jEadditionally moves the original archive to the Trash once extraction has actually succeeded — asking first, and naming the fallback plainly as a real permanent delete, if that ever fails outright. All of this runs in the background, the same as Copy/Cut/Paste — no terminal takes over the screen, and the affected directory reloads on its own once it's actually done. -
Three rows below the panel, each with its own job. First, a real shell command line (with its own history — shared with
$HISTFILEif you've set it,~/.bash_historyotherwise regardless of your actual shell, a deliberate choice so this line's own history always behaves like a bash one's — andcdhandled directly rather than uselessly changing a subshell's own directory), which expands when clicked into — full width, no prompt, growing upward toward mid-screen with a "Bash Prompt Editor" legend above it — for multi-line bash scripting (Enter runs the buffer, Ctrl+J or Alt+Enter inserts a newline instead — Ctrl+J always works, Alt+Enter is intercepted by some terminal emulators for their own use; Up/Down recall history too, not just Ctrl+P/Ctrl+N, except at a line a multi-line script is still being composed on; Tab completes the filename/directory at the cursor against the panel's own current directory, or, when several matches agree on nothing further, opens a scrollable pick list of them instead of doing nothing). Every command runs through your actual$SHELL, started the same way an interactive login shell would be — that shell's own startup files, aliases and functions sourced first, so something likellworks exactly as it does in a real terminal — the same way Midnight Commander's own command line handles every command — no attempt to guess which programs need one and which don't. Its own output stays on screen until you press Escape to return, so it doesn't just flash by. -
A middle row, always visible right below the command line, showing a curated subset of the keyboard layer's own letters as a quick legend — each key set off in its own petrol background, one space either side, so the letter-to-action mapping reads at a glance: Copy (
c), Cut (x), Paste (v), Look (l), Edit (e), Execute (E), Move to Trash (d), Rename (r), Properties (i), Split view (s), Connect… (@), the tab switcher (t), toggle hidden files (.— labeled Hide or Unhide, whichever it would do next, not whichever state you're currently in), and Help (?), plus the six chord families marked with an ellipsis and no space after it to show they lead to more keys (g…goto,p…perms,m…menu,z…disp,o…opts,j…tools) — every member of an open chord's own legend is clickable too, the same highlighted-key treatment, so pointing at one works as well as typing its second letter. Details sidebar (I) doesn't have its own slot in this row any more — it was crowding it — but is still fully live from the keyboard and the header's own<button. A few of these change meaning while actually browsing the trash itself:dasks to remove permanently instead of moving something already-trashed to the trash again,rrestores instead of renaming, andDempties the whole trash instead of removing just the selection — the button labels themselves stay put either way, since a bar that changed shape underfoot would be its own kind of confusing. Every entry here is also reachable from the context menu, whose own status-line legend follows the exact same pattern one level into a submenu, complete with a clickable◂ Backbutton of its own. Every key is fully documented (including everything that doesn't fit this one row) in the in-app help (?), and each still works the same way whichever panel or field currently has focus, except while the command line itself is expanded and needs those same keys for its own editing. Hidden-files/size-format/mtime-format toggles are remembered across restarts. -
A bottom row that's purely informational, no buttons on it at all: the current user (green, red while running as root), disk space (
free/total, labeled with the real filesystem type —EXT4,CIFS,NFS4,ECRYPTFS, ... — the same one the Mounts screen shows) and inode usage (used/total, omitted on a filesystem that can't report a count at all) for the directory on screen — each with its own fixed color and a green/orange/red percentage (under 80% / 80%+ / 90%+) — git status (git:(branch) ⇡ahead ⇣behind +staged !unstaged ?untracked =conflicts, the same phrasing several zsh prompt themes already use, green/orange/red for clean/dirty/conflicted, shown only inside a git repository), the running kernel (uname -r), uptime and load average where the platform exposes them (Linux's own/proc/uptimeand/proc/loadavg— quietly omitted elsewhere rather than shown wrong; load's own three numbers are colored against this machine's core count, not as raw, meaningless-alone figures), and a clock. Every one of those eight segments can be switched off on its own from Options → Status bar. See docs/user-guide.md for the full breakdown. The real filesystem root shows a selectable "/" row in place of the usual..; selecting it and opening the Details sidebar (I) shows a much fuller version of this same overview — plus memory, swap, open file handles, mounted filesystems, process count, network interfaces and logged-in sessions, in the same colors and green/orange/red scale — while every other entry under "/" still gets its own ordinary per-file Details. On a remote connection's own "/" this describes that machine instead, sourced straight from its own/proc//etc/os-releaseover the same connection, no separate command-execution channel needed — except logged-in sessions, which does need one and is simply left out remotely. Selecting any other directory that's part of a git repository adds that same git status line as its own section there too, fetched a moment after the cursor settles and cancelled if it moves on, so scrolling through a long list of directories never queues up onegit statusper row. See docs/user-guide.md for the full field list. -
Remote connections (SFTP): a compact
@button sits right before the path itself in the header — muted for a plain local panel, a slow pulse toward a lighter green and back once connected (never dipping toward black — that reads as "still searching for a signal", not a settled, already-alive connection) — opening a dropdown (also reachable via the plain@key, or thegchord's owngc), styled and shaped like the tab switcher: + New connection plus recent history, each row its own independently clickable cells rather than markup-colored text — bright green for the one active in this panel, a matte dimmer green for one that has connected successfully before but isn't active now, red for one that used to work and just failed, plain/uncolored for one saved but never actually dialed yet (see the Connect dialog's own "OK" button below — a connection that failed its very first real attempt isn't added to history at all, unlike one saved without ever trying). The active row's own trailing⏏(or the "e" key) disconnects, right next to the✕(or "x"/Delete) every row already has to drop it out of history — every other history row carries a✎in that same spot instead ("e" there too), opening the Connect dialog prefilled from it without immediately retrying, so a saved Host/Port/User can be fixed before reconnecting. Every row is a realUser | @ | Host/IPcolumn table, not one merged string —Useris the one keyboard stop for the whole label group (clicking anywhere across@/Host/IPstill connects too). The Connect dialog itself has three buttons —Cancel/OK/Connect— withOKas the default (Enter in any field runs it): it only ever saves the typed fields into history, never dials, so several connections can be prepared ahead of the servers behind them actually existing. Opening via "+ New" or✎showsOK; clicking a history row's own label skips the buttons entirely and dials immediately, same as before. Editing (✎) shows onlyCancel/OK— no way to dial from there. Saving edited Identifier/Tags throughOKnever moves the entry or touches its real green/red status; saving under a changed Host/Port/User creates a new, separate, plain-colored entry instead of renaming the old one. An optional "Identifier" field on the Connect dialog tells apart several similarly-named connections that otherwise only differ by host; a second, optional "Tags" field (comma-separated, up to 10, each up to 32 characters) adds a set-valued way to group and filter connections — normalized (trimmed, deduplicated, sorted) once the field loses focus, never while still typing.</>on the dropdown's own title bar adds both as a fourth and fifth column and spans the whole terminal width instead of just the active panel, remembered across restarts (connection_menu_expanded). Each column has a label above it (User/Host/IP,Identifiertoo once expanded) — click one, orU/H/Iwhile the dropdown has focus, to sort by it (a second click/press of the same one reverses it), shown with an↑/↓suffix; the header itself is mouse-only, not reachable by arrow keys, and sorting resets to "most recently used first" the next time the dropdown opens.Tagshas no mnemonic and isn't sortable, only filterable. An always-present "Filter:" field fills the rest of the top row — click it, press/, or Tab/Shift+Tab from the list (the dropdown's only two real keyboard stops, so either direction reaches it); Escape, Enter, or Tab/Shift+Tab again returns to the list without clearing what was typed (Escape again closes the dropdown). Narrows the list live across User/Host-IP (and Identifier/Tags once expanded) using the same AND/OR/NAND/NOR/NOT expression grammar as Log Holepunch's own keyword field; resets empty the next time the dropdown opens, same as sorting. Connecting dials in the background with a live progress line; authentication tries anssh-agentfirst, then the usual default key files (~/.ssh/id_ed25519,id_ecdsa,id_rsa), then a typed password as the last resort. Host keys are checked against the real~/.ssh/known_hosts— an unknown host raises a trust-on-first-use prompt, exactly like a realsshclient's own "authenticity of host" question, while a host whose key changed is always rejected outright with no automatic bypass — the dialog shows both fingerprints and offers a "Connect anyway" retry that skips the check for that one attempt only, never saved toknown_hosts. Once connected, the panel browses the remote filesystem exactly like a local one — same columns, same sorting, same navigation, and Details/status-bar Disk+Inodes/System Info at "/" all describe the remote machine, not this one. Rename, permanent delete, chmod (including its recursive dirs/files options), and Copy/Cut/Paste all work against a remote target too, through the exact same Overwrite/Merge/Skip conflict dialog a purely local Paste already has, rather than an older engine that just refused outright the moment a destination already existed; "d" (Move to Trash) explains why it switches straight to a permanent-delete confirmation instead, since a remote session has no trash to move into. Look and Edit stage a real local temp copy behind the scenes and hand it to the ordinary built-in viewer/ external pager/$VISUAL/$EDITOR unchanged — Edit only uploads it back if it actually changed. Opening a zip/tar that itself lives on a remote connection downloads and browses it the same transparent way; above a configurable size (Options → Remote connections, KB/MB/GB, 10MB by default) it asks first, naming the real size. Copying a member back out of a remote archive works too, straight from the local temp copy already downloaded to browse it — to a local destination directly, or to another remote directory via a local staging round trip. Still refused with a clear message for now: chown, Compare, Batch rename, Sed Replace, and Properties as a whole. See docs/user-guide.md for the full picture. -
Color schemes: JSON files under
colorschemes/in either config tier (see below), switchable live from the Options screen (theochord's ownoo) — no restart needed, and the pick is remembered for next time. -
Search (
f): by file name (glob, a plain keyword, or regex — viafind, orlocatewhere its own index is available) or by file content (grep, and — where installed —zgrep/zipgrepfor gzip/zip contents), scoped to any directory, with real-time streamed results you can jump straight to. Runs the real system tools rather than a reimplemented search, so it inherits whatever's already indexed bylocate's ownupdatedb. The results view isn't a dead end either: its own header carries a real, fully clickable/editable path breadcrumb — starting at the search's own scope — right alongside the status line, so you can keep browsing normally without first jumping to a specific hit or backing all the way out with Escape. -
Look (
l, the bottom bar's own button, or the context menu): a read-only, full-screen preview of the selected file's content. Plain text, source code, config files, diffs/patches, and logs get built-in syntax coloring (~200 languages, no external dependency — large files show their own first 8 MiB rather than being fully loaded). PNG, JPEG, GIF, BMP, TIFF, and WebP images render right in the terminal — decoded and scaled entirely in Go, no external tool needed there either. A format Look doesn't have a decoder for at all (HEIC, AVIF, RAW, ...) still opens the same overlay, with a recommendation for an external tool that can (chafa, orpixtermif you'd rather stick to Go-only tooling) — matched to whichever package manager your system actually has. Setpager = externalin your config instead (see Color schemes below for the file itself) to openbat/batcat,$PAGER, orless/morein your real terminal for text. "Tail -f", right next to Look in the context menu, follows a growing log live via the realtail -f. PDFs open page by page — as a real rendered page image where poppler-utils'spdftoppmis installed (feeding straight into the same image renderer above), or as extracted plain text otherwise, entirely in Go, no external tool required.PageUp/PageDownmove between pages either way;g/tswitch a given page between rendered-image and extracted-text view on demand — handy since a text-heavy page rendered as an image downsamples into illegible mush at any realistic terminal size. A video file (MP4, MKV, WebM, AVI, MOV, and the rest of the usual container formats) hands the real terminal over to mpv instead, for full-screen playback — no graphical window needed, mpv falls back to its own true-color terminal renderer over SSH/tmux/screen.qorCtrl+Cquits back into breakthrough. The Details sidebar shows the same video as a still-frame thumbnail, grabbed via mpv 10% into its own duration to skip past a typically black/blank opening. -
Archive browsing:
Enteron a.zip,.tar,.tar.gz,.tar.bz2,.tar.xz,.7z, or.rarfile opens a browsable listing of its own contents right in the panel, exactly like a real directory —..steps back out to the real one it lives in. Mark entries withSpaceand Copy (c) them out to a real destination with Paste (v), nested folders and all. zip and tar are read entirely in Go, no external tool needed; 7z and RAR shell out to the real7zandunrarbinaries instead — neither format has a pure-Go story this project is willing to depend on (7z: no actively maintained, complete reader; RAR: a proprietary format with no legally clean Go implementation at all). Read-only otherwise, by design: renaming, editing, chmod/chown, Cut, and Move to Trash/Remove are all refused while browsing inside one. An archive found inside another is never opened this way automatically — it stays a plain file, extracted whole if you copy it out. -
CSV/TSV tables: Look renders a
.csv,.tsv, or.tabfile as a plain, fixed-width-aligned table instead of its own raw delimited text — each column padded to its own widest cell, the same shape the POSIXcolumn -tcommand already produces. Falls back to ordinary syntax-highlighted text if a file merely named that way isn't actually parseable as delimited data. -
Bare
.gz/.xzfiles: Look decompresses one to a temp file first, then shows it exactly as if it had never been compressed — a rotated log (syslog.1.gz,kern.log.4.xz, ...) opens the same way its own uncompressed, currently-active version already does. A.tar.gz/.tgz/.tar.xz/.txzis left alone (it's a real multi-file container, already browsable by entering it like a directory — see Archive browsing above); decompressed content that isn't actually text still gets Look's own ordinary "no viewer for this file type" response. -
Activity log: off by default, turned on under Options with a single
log_leveldial —errors,actions,detailed, ordebug, each a superset of the one before it — plus eight independent on/off categories (file operations, permissions, archives, Sed Replace/Batch Rename, Rsync, remote connections/transfers, shell, firewall rules) so you can dial detail up or down per subsystem rather than all at once. Plain, greppable text, one line per entry, written to/var/log/breakthrough/breakthrough.logwhere that's writable or the same per-user directory the crash log already uses otherwise —actionsrecords every real, state-changing action (Copy, Rename, chmod, Compress, a Rsync run, an SFTP connect…), enough to answer "what did I actually do with breakthrough" after the fact, and the foundation a future Undo will build on. An Action Log screen (jl) browses the real log file itself — newest entries first, with a keyword filter (full text over the message) and a time-range filter reusing the panel's own Modified-time filter syntax. -
Log Audit (
jL): reads other log files on disk — whichever directory the active panel currently shows, no arguments or flags. Discovers logrotate families (access.log,access.log.1,access.log.2.gz, ...), lets you check which ones to include, then merges them into one chronological stream with automatic format detection (JSON Lines — including Docker's json-file log driver, syslog RFC 3164/5424, a generic "timestamp level message" line, or plain text as a fallback), transparent.gz/.xz/.zst/.bz2decompression, a live keyword filter — a boolean query (AND/OR/NAND/ NOR/NOT, parentheses for grouping, e.g.keyword1 and (keyword2 or keyword3)) over Source and Message, not just a flat substring — with match highlighting, a Time field (same syntax as the Action Log) and a minimum-severity Level field alongside it, a mini-timeline showing event density colored by severity, a Statistics overlay (counts by level and top sources), "e"/"w" to jump between errors and warnings, a follow mode ("f") that re-reads every two seconds, and a raw-line section in the detail view whenever the original line differs from the extracted message. -
Git breach (
jg): a multi-pane dashboard for the git repository containing the active panel's own current directory, shelling out to the system's owngitrather than reimplementing it. Status (branch, ahead/behind, a one-line summary), Files (staged/unstaged/ untracked, conflicts first, syntax-colored diffs — Space stages/ unstages the row under the cursor), Branches (every local branch, checkout on Enter, delete with "d"), Commits (newest first, colored by author, a lane/branch graph for merges, loads 200 at a time) and Stash (apply with "a", drop with "d") are all live. The Main pane on the right shows whichever side box currently has focus — Files' own diff, an untracked file's raw content, or a Commits/Stash row's full diff — and can take real keyboard focus itself (Right/Enter/Space to jump in, arrow keys or PgUp/PgDn to scroll, Left/Enter/Space/Esc to step back out), remembering each file/commit/stash entry's own scroll position for the whole session. "c" commits whatever is currently staged. Local working trees only — there is no command-execution channel to run git against a remote session.
Actively developed and usable day to day. Everything described above is
built and tested: browsing, tabs, split view, the trash, Find, Look,
archive browsing, Sed Replace, Batch rename, Compare, a full Options screen
covering every setting breakthrough recognizes, a Toolbox screen of
built-in networking and hardware tools with its own Network Tools
(jn) and Hardware Tools (jh) screens, a Mounts
screen (jm) showing what's currently mounted and whether it survives a
reboot, a Firewall screen (jf) showing this host's own actual
firewall rules (UFW, nftables, or iptables — whichever one really
governs traffic), including which rules shadow each other, plus an
Add-rule form (a, UFW/iptables only) that builds the exact command
for a new rule, confirms it before running it via sudo, and
automatically reverts an SSH-relevant rule if it isn't explicitly kept
within 30 seconds, a Simulate form (t, every backend including
nftables) that reports which rule, if any, decides a hypothetical
request, an Action
Log screen (jl) browsing the real activity log with keyword/time
filtering, a Log Audit screen (jL) reading and merging other log
files found in whichever directory the active panel currently shows,
a Git breach dashboard (jg) for staging, committing, and diffing the
local git repository containing the active panel's own current
directory — its own diff pane toggles between a unified pager and a
side-by-side view (v) — a Sessions screen (js) listing local GNU screen, tmux, and
Zellij sessions side by side with per-row Attach/Close actions (Attach
hands the real terminal to the session — screen -D -r, tmux attach -d, or zellij attach — and returns to breakthrough automatically once
you detach or it ends), an SSH Keys screen (jk) inventorying local key
pairs under ~/.ssh — algorithm/size, passphrase protection, file
permissions, and whether each is currently loaded in the running
ssh-agent — plus a Generate key form (a) that builds the exact
ssh-keygen command for a new pair and runs it through a real
terminal, deliberately with no passphrase field of its own (ssh-keygen
prompts for one interactively instead, so it never sits in a process
argument list or shell history), a Copy to server form (c, the same
end result ssh-copy-id produces — never a duplicate, existing
authorized_keys backed up first, strict permissions set explicitly —
followed automatically by a non-interactive check that the new key
alone now actually works passwordless), real
Compress/Extract (jc/je/jE) through zip, tar, gzip, bzip2, xz,
zstd, 7z, and (extract-only) RAR, and a notification system for
background jobs finishing while
you're looking elsewhere (a backgrounded Rsync run, the paste/move
queue, the Firewall screen's own self-lockout rollback) — a transient
status-bar toast, a persistent unread badge, and a full Messages screen
(gm) with dwell-based auto-read, persisted across restarts as plain
JSON, and an Email launcher (ge) for whichever terminal mail client
is installed (neomutt, aerc, himalaya, mail, or mailx — configurable
under Options when more than one is present), plus a status-bar mail
badge ("Email") showing an unread count from a configured Maildir or mbox
mailbox, or the current user's own system mailbox (/var/mail),
auto-detected. Progress bars for long-running file operations are
what's planned next — see
docs/whitepaper.md for the full concept and
vision, and follow along or join in on
Discussions.
The current package and function relationships are shown in the generated
callgraph below. It is regenerated with go-callvis for changes merged into
develop and for tagged releases.
breakthrough ships with one built-in scheme ("Default") and reads
further ones from colorschemes/*.json in either config tier —
/etc/breakthrough/colorschemes/ for system-wide schemes,
~/.config/breakthrough/colorschemes/ (or $XDG_CONFIG_HOME/breakthrough/colorschemes/
if set) for your own; a user file with the same name replaces a system
one. Switch between whatever's found via the Options screen (the o
chord's own oo) — the pick applies immediately and is saved to your
own ~/.config/breakthrough/config.
The .deb/.rpm packages (see Installing) create
/etc/breakthrough/config and /etc/breakthrough/colorschemes/ for
you — the config file already fully documented and commented out, ready
for a system administrator to uncomment and edit — so a package install
gives you a real, discoverable starting point rather than a path that
simply doesn't exist yet. A plain .tar.gz install has no installer to
do that for it; the Options screen's own "Edit config file" button
creates your user config the same way, on first use, if you'd rather
start from that tier instead.
Two ready-made examples ship in examples/colorschemes/
— a dark Solarized-based scheme and a light one:
mkdir -p ~/.config/breakthrough/colorschemes
cp examples/colorschemes/*.json ~/.config/breakthrough/colorschemes/A scheme file only needs to set the fields it actually wants to change —
anything left out falls back to the Default scheme's own value. Each
color is either a #rrggbb hex value or a
W3C color name
(e.g. "darkslategray"). See
examples/colorschemes/solarized.json
for every field a scheme can set. The semantic UI roles are
surface_background for normal content, popup_background for menus and
dropdowns, input_background/input_focused_background/
input_disabled_background for input states, selection_background for
selected rows, and button_background/button_focused_background for
buttons. text_color, muted_text_color, and border_color provide the
corresponding foreground and chrome roles. The older accent_background,
focused_background, editable_background, text, and placeholder_text
fields remain supported as compatibility fallbacks for existing schemes.
Every release ships prebuilt binaries for all supported platforms on the
Releases page.
No runtime dependencies: breakthrough is a single static Go binary built
with CGO_ENABLED=0, so there is nothing to install alongside it and
nothing to break on a libc upgrade.
| Your system | Architecture | Download |
|---|---|---|
| Linux, ordinary PC/server | x86_64 / amd64 | breakthrough_<version>_linux_amd64.* |
| Linux, Raspberry Pi 4/5 (64-bit), ARM servers, AWS Graviton | aarch64 / arm64 | breakthrough_<version>_linux_arm64.* |
| macOS, Intel | x86_64 | breakthrough_<version>_darwin_amd64.tar.gz |
| macOS, Apple Silicon (M1–M4) | arm64 | breakthrough_<version>_darwin_arm64.tar.gz |
| FreeBSD | x86_64 / amd64 | breakthrough_<version>_freebsd_amd64.tar.gz |
Not sure which one you're on? uname -sm answers both questions at
once — Linux x86_64 means linux/amd64, Linux aarch64 means
linux/arm64, Darwin arm64 means Apple Silicon.
32-bit builds (i386, armv6/armv7) are deliberately not published. If you need one, it cross-compiles from source in one command — see Building from source below.
VERSION=0.18.0 # or whatever the latest release is
ARCH=$(dpkg --print-architecture) # amd64 or arm64
curl -LO "https://github.com/jagottsicher/breakthrough/releases/download/v${VERSION}/breakthrough_${VERSION}_linux_${ARCH}.deb"
sudo apt install "./breakthrough_${VERSION}_linux_${ARCH}.deb"apt install ./file.deb rather than dpkg -i so any dependency
resolution is handled for you. Upgrade by installing a newer .deb the
same way; remove with sudo apt remove breakthrough.
VERSION=0.18.0
ARCH=$(uname -m) # x86_64 or aarch64
case "$ARCH" in x86_64) PKG=amd64 ;; aarch64) PKG=arm64 ;; esac
curl -LO "https://github.com/jagottsicher/breakthrough/releases/download/v${VERSION}/breakthrough_${VERSION}_linux_${PKG}.rpm"
sudo dnf install "./breakthrough_${VERSION}_linux_${PKG}.rpm" # or: sudo zypper install ./...Both packages also install brk as a short alias for breakthrough — the
same binary, invoked under either name.
Both packages also create /etc/breakthrough/config — fully documented,
every setting listed and commented out — and /etc/breakthrough/colorschemes/,
so a system administrator has a real starting point for machine-wide
defaults (see Color schemes). Your own edits to that
file survive a package upgrade: it's registered as a conffile on Debian
and %config(noreplace) on RPM.
Works on any distribution, with or without root:
VERSION=0.18.0
OS=$(uname -s | tr '[:upper:]' '[:lower:]') # linux, darwin, freebsd
case "$(uname -m)" in
x86_64|amd64) ARCH=amd64 ;;
aarch64|arm64) ARCH=arm64 ;;
esac
curl -LO "https://github.com/jagottsicher/breakthrough/releases/download/v${VERSION}/breakthrough_${VERSION}_${OS}_${ARCH}.tar.gz"
tar xzf "breakthrough_${VERSION}_${OS}_${ARCH}.tar.gz"
sudo install -m 0755 breakthrough /usr/local/bin/breakthrough
sudo install -m 0644 breakthrough.1.gz /usr/local/share/man/man1/
sudo ln -sf breakthrough /usr/local/bin/brkWithout root, drop it somewhere on your own PATH instead:
mkdir -p ~/.local/bin && install -m 0755 breakthrough ~/.local/bin/
mkdir -p ~/.local/share/man/man1 && install -m 0644 breakthrough.1.gz ~/.local/share/man/man1/
ln -sf breakthrough ~/.local/bin/brk(The .deb/.rpm packages install the man page and the brk alias
automatically — no separate step needed there; see man breakthrough
once installed.)
On macOS, Gatekeeper quarantines anything downloaded with a browser. If you get "cannot be opened because the developer cannot be verified", clear the quarantine flag once:
xattr -d com.apple.quarantine /usr/local/bin/breakthroughDownloading with curl, as above, avoids that entirely.
Every release includes a checksums.txt covering all its artifacts:
curl -LO "https://github.com/jagottsicher/breakthrough/releases/download/v${VERSION}/checksums.txt"
sha256sum --ignore-missing -c checksums.txt # shasum -a 256 on macOS/FreeBSDNeeds only a current Go toolchain — no C compiler, no system libraries:
git clone https://github.com/jagottsicher/breakthrough.git
cd breakthrough
go build ./cmd/breakthroughCross-compiling for another machine is a matter of two environment variables, which is also how to get an architecture the releases don't cover:
GOOS=linux GOARCH=arm GOARM=7 CGO_ENABLED=0 go build ./cmd/breakthrough # 32-bit Raspberry Pi
GOOS=linux GOARCH=386 CGO_ENABLED=0 go build ./cmd/breakthrough # 32-bit x86
GOOS=openbsd GOARCH=amd64 CGO_ENABLED=0 go build ./cmd/breakthrough # OpenBSDSee CONTRIBUTING.md for the full development setup, and docs/installation.md for terminal requirements, the files breakthrough creates on your system, and uninstall instructions.
- docs/user-guide.md — every feature in
detail, the complete settings reference, and a full keyboard map.
?inside the application shows a condensed version of the same thing, always matching the build you're running. - docs/installation.md — terminal requirements, exactly which files land where, optional external tools, system-wide rollout for administrators, upgrading, uninstalling, and troubleshooting.
- docs/whitepaper.md — the concept and the reasoning behind the project.
- docs/code-index.json — a generated, deterministic source index with file hashes, a Merkle root, Go package imports, and declared symbols for tooling and architecture work.
- CONTRIBUTING.md — local setup and the branch workflow.
Contributions are very welcome — this is an ambitious project for one person alone. See CONTRIBUTING.md for local setup, the branch workflow, and code style expectations.
The best way to support breakthrough is to participate and contribute. If you'd like to leave a tip instead, you can send a few Satoshis via the Lightning Network ⚡️ using the sponsor button above.


