One development environment. Five targets. The same experience on each.
red-dev installs the same tools, shell, terminal, tiling and theme on bare-metal
Ubuntu, on WSL, and on native Windows — from a single binary that needs no
runtime, no bash on Windows, and no second configuration to keep in sync.
Built on the ideas of Omakub by DHH and Basecamp — credit where it is due.
| Getting there | What it is | Living with it |
|---|---|---|
| Quick start | The support matrix | Usage |
| Attribution | The identical layer | Using it on each target |
| Develop | One directory both sides read | Themes |
| Status | Under the hood | Troubleshooting |
Linux and WSL:
curl -fsSL https://raw.githubusercontent.com/reddb-io/red-dev/main/boot.sh | shNative Windows:
irm https://raw.githubusercontent.com/reddb-io/red-dev/main/boot.ps1 | iexBoth resolve the binary for your platform from the latest release, install it
under your own user, and then open red-dev's own interface — the same screen
you get by typing red-dev, where you choose between a first install and
maintenance. The bootstrap and the binary arrive at the same place on purpose;
running the one-liner is how you get the product, not a different, shorter
version of it.
Neither needs administrator or root rights for red-dev itself; individual packages may still ask for sudo.
On Windows, open a new terminal after installing — the PATH entry does not
reach shells that were already running.
Every push to main also publishes a next prerelease, so the newest work is
installable without waiting for a tag:
RED_DEV_CHANNEL=next sh -c "$(curl -fsSL https://raw.githubusercontent.com/reddb-io/red-dev/main/boot.sh)"$env:RED_DEV_CHANNEL='next'; irm https://raw.githubusercontent.com/reddb-io/red-dev/main/boot.ps1 | iex| Variable | Effect |
|---|---|
RED_DEV_CHANNEL |
stable (default) or next — every push to main publishes a next prerelease |
RED_DEV_BIN_DIR |
Where the binary lands; defaults to ~/.local/bin (Linux) or %LOCALAPPDATA%\red-dev\bin |
GITHUB_TOKEN |
Raises the API rate limit; required only for a private fork |
This project is inspired by, and derived from, Omakub by David Heinemeier Hansson and Basecamp. The omakase philosophy, the curated tool selection, the aliases, the minimal prompt, the LazyVim setup and the theme system all started there, and the credit for them is his.
Omakub targets Ubuntu 24.04 on the desktop. red-dev runs that environment on Ubuntu 24.04 and 26.04, inside WSL and on native Windows — one binary, one configuration, no second copy to keep in sync. Where the two disagree it is about portability, never about taste.
Also built on tuiuiu.js for the interactive layer and cli-args-parser for the command surface.
| Ubuntu 24.04 | Ubuntu 26.04 | |
|---|---|---|
| Desktop, bare metal | target 1 | target 2 |
| WSL | target 4 | target 5 |
| Windows, native | target 3 — no distro axis |
Two axes, not five cases. Code branches on where it runs and what that place can do, never on a raw version string. Ask a machine what it is:
red-dev platformos=linux distro=ubuntu version=24.04 env=wsl arch=x64
caps: apt=1 gui=0 systemd=1 winget=1 flatpak=1os=windows distro=n/a version=n/a env=windows arch=x64
caps: apt=0 gui=1 systemd=0 winget=1 flatpak=0Read the first as: a WSL distro with no display of its own, but able to reach
the Windows host through winget. That single pair — gui=0 winget=1 — is why
the WSL target installs fonts and terminal configuration on Windows rather
than inside Ubuntu, where they would accomplish nothing.
Adding Ubuntu 28 should mean touching src/platform.ts and
the manifest, nothing else.
The terminal layer is the same on all five targets, because every piece of it has a real build on every one of them.
| Alacritty | terminal | one alacritty.toml, one theme file |
| zellij | panes, tabs, sessions, layouts | one config.kdl, works over SSH |
| bash | shell | one set of dotfiles, Git Bash on Windows |
Alacritty deliberately has no panes, and the platform-native tiling answers share nothing — GNOME extensions on Linux, FancyZones on Windows. Putting the panes inside the terminal is what makes tiling identical everywhere, and it keeps working over SSH, which no window manager can offer.
On native Windows the terminal launches Git Bash, not PowerShell. That is what makes the shipped dotfiles apply there at all, and it is why standardising on bash rather than PowerShell is what makes "same experience" true instead of aspirational.
Every interactive shell starts inside zellij. omakub gets this by pointing
Alacritty's shell at zellij, which works because there the terminal and the
multiplexer are the same machine's programs. Here they are not: under WSL and
Git Bash the terminal is a Windows program and zellij is not, and Windows
Terminal, the VS Code terminal and every SSH session never read Alacritty's
config at all.
So it starts from config/bash/zellij.sh instead —
the one layer every target shares. One behaviour in the terminals red-dev
configures and in the ones it does not, and on the far end of an SSH
connection, which no terminal config can reach. It also settles TERM: a pane
gets xterm-256color everywhere, rather than alacritty in one place and
something else in another.
It stays out of the way where a multiplexer would break things — non-interactive
shells, no tty, tmux, the VS Code and JetBrains terminals, nvim's :terminal,
TERM=dumb. And it does not exec: a zellij that cannot start leaves you in a
plain shell with a message, because the alternative is a terminal that closes as
fast as it opens and no way to edit the dotfile that would fix it.
Turn it off with RED_ZELLIJ=0 in ~/.config/red-dev/env.sh.
Because zellij is now always on, its own keybindings would be taking Ctrl-p,
Ctrl-t, Ctrl-n, Ctrl-o and Ctrl-s from the shell that had them first. So
config.kdl starts in locked mode with the defaults cleared: Ctrl-g
unlocks, every binding returns to locked, and Alt plus arrows or hjkl moves
between panes without leaving it.
The clipboard is one behaviour reached three ways. On a real Linux desktop
zellij's copy command targets wl-copy, so wl-clipboard is a declared
desktop dependency rather than a tool you are assumed to already have. WSL and
native Windows cross to the Windows clipboard instead, and there text stays
Unicode: zellij sends UTF-8, and red-dev's low-latency bridge converts it to
BOM-less UTF-16LE before clip.exe reads it. That avoids both clip.exe's
OEM-code-page mojibake and zellij's one-second timeout for clipboard commands. Mouse selection copies automatically. In
Alacritty, paste is Ctrl+V or Ctrl+Shift+V; Ctrl+Shift+C copies an
Alacritty selection, while plain Ctrl+C remains the application's interrupt
key. Inside a mouse-capturing TUI such as herdr, normal drag selection belongs
to herdr and is copied automatically; hold Shift while dragging to select in
Alacritty instead, or use herdr's copy mode (Ctrl+B, then [).
git · curl · ripgrep · fd · bat · eza · zoxide · fzf · btop ·
jq · fastfetch · gh · lazygit · lazydocker · zellij · mise ·
neovim · docker · delta · yazi · tldr · starship · atuin ·
carapace · direnv
The RedDB tools come with it, because this is the environment a RedDB developer works in:
red |
the RedDB CLI | every target |
tq |
query and convert TOON | every target |
red-request |
API client, powered by recker | desktop sessions |
red-ui |
universal client for reddb | desktop sessions — see below |
dit |
push-to-toggle voice dictation | desktop sessions |
herdr |
several agents in one terminal, alive over SSH | Linux and WSL |
red and tq are CLIs, so they are core and land on all five targets.
red-request, red-ui and dit are desktop, which also means WSL never
attempts them: installing a Linux GUI app inside a distro with no display is the
mistake this project exists to avoid, and the Windows target already covers that
same machine.
dit is a CLI and still desktop, for the same reason wearing different
clothes — it types into the focused application, and under WSL there is
neither one of those nor a /dev/input to read its hotkey from. On Linux it
comes from the publisher's installer rather than the release binary, and not for
the usual checksum reason: dit reads its hotkey from /dev/input and types
through /dev/uinput, so it needs you in the input group and a udev rule.
Dropping the binary in gives you a program that runs and cannot see a keypress.
red-dev passes --yes to keep the converge non-interactive and --no-service
to leave the autostart unit alone — a standing background service is a decision
to make deliberately, and dit works without it. It also wants an
ELEVENLABS_API_KEY, or --engine local for offline Whisper.
Coding agents are chosen rather than assumed — red-dev agents offers
claude-code, codex and opencode pre-ticked, plus gemini, herdr,
openclaw, hermes, and the Claude, Codex and T3 Code desktop apps on
Windows. herdr is not an agent but the thing agents run inside — it
multiplexes several into one terminal and keeps them alive across an SSH
disconnect. Each
installs by the path its publisher supports rather than one uniform mechanism,
and whose path it is gets checked: winget has no Google entry for Gemini —
searching it returns third-party chat clients that merely speak to Gemini — so
that one is npm, while T3 Code went the other way, because npm's t3code-cli is
a third-party wrapper and winget's T3Tools.T3Code is the publisher's own.
Picking any CLI agent then offers
red-skills, which registers its
marketplace in Claude Code and Codex and generates plugin modules for OpenCode.
Web apps — a page in its own window, its own icon and its own alt-tab entry, the way omakub's web2app does it. Desktop sessions only: a .desktop file needs a menu to appear in.
Optional, never installed by a plain converge — red-dev apps offers them,
and so does the interview: just · duf · dust · hyperfine · glow ·
gitui, plus powertoys on Windows.
Runtimes are mise's, not the distro's, so node resolves the same way in
WSL, on the desktop, and in Git Bash. red-dev lang chooses which. A version
manager that manages nothing is how pnpm ends up working in one shell and not
another on the same machine.
Aliases that normalise Debian's renames (bat → batcat, fd → fdfind), the
readline bindings that put history search on the arrow keys, autocd,
cdspell, globstar, a curated git alias set, and shell functions like
webm2mp4 — and the integrations that make the tools above actually do
something rather than merely exist. A shipped function is only honest if what it
shells out to is present, so webm2mp4's ffmpeg dependency is declared as a
core tool rather than assumed:
Docker is one daemon, never two. Under WSL, if Docker Desktop already serves
the distro, red-dev does not install docker-ce — a second daemon means
containers started on one side are invisible to the other, on separate networks,
with no error from either. red-dev doctor reports which daemon answers.
| Tool | Without the integration it is |
|---|---|
delta |
a pager git never calls |
yazi |
a browser you must cd after |
tldr |
"Page cache not found" |
atuin, carapace, direnv |
binaries nothing is bound to |
ble.sh — autosuggestions and syntax highlighting, the two things people most
often miss from zsh — is installed but not enabled. It replaces bash's line
editor rather than sitting beside it, and atuin, fzf and carapace all bind into
what it replaces. Whether they survive is an empirical question that needs a
real terminal, so turning it on is deliberate:
export RED_BLE=1red-dev # the fullscreen interface — and what the one-liner opens
red-dev platform # what red-dev thinks this machine is
red-dev plan [scope] # what would change, changes nothing
red-dev install [scope] # converge toward the manifest
red-dev install --dry-run # print the plan, touch nothing
red-dev update # upgrade what the package managers own
red-dev theme [name] # tokyo-night | catppuccin | gruvbox
red-dev apps # choose optional tools
red-dev lang # choose runtimes for mise to manage
red-dev shell # Windows + WSL: where a terminal lands
red-dev agents # choose coding agents, wire in red-skills
red-dev share [path] # one directory both WSL and Windows read
red-dev share adopt <tool> # move that tool's configuration into it
red-dev uninstall # remove tools, or red-dev's own config
red-dev wsl # Windows: set up or verify WSL 2
red-dev ui # fullscreen, with live theme preview
red-dev doctor # report tool and configuration driftGlobal options: --theme, --font, --opacity. Scopes: core, desktop,
wsl, optional.
Most of what this tool does is answer questions — what is this machine, what would change, what has drifted — and none of those are worth installing something to ask. Pass a command to the bootstrap and it runs from a temporary copy that deletes itself:
curl -fsSL https://raw.githubusercontent.com/reddb-io/red-dev/main/boot.sh | sh -s -- doctor:: resolving stable release of reddb-io/red-dev
:: downloading red-dev-linux-x64 (temporary)
:: running: red-dev doctorinstall never prompts. It runs in CI, in scripts, and over SSH, where a
question is a hang — so every choice lives behind a command you invoke
deliberately:
| Command | Asks |
|---|---|
red-dev |
the fullscreen interface, then whatever you pick — a line-based menu below 60 columns, and --help with no terminal at all |
red-dev theme |
which theme, when given no name |
red-dev apps |
which optional tools — ticked, minus anything that says it is too large to be a default |
red-dev lang |
which runtimes mise should manage |
red-dev shell |
whether a terminal lands in WSL or Git Bash |
red-dev agents |
which coding agents — pre-ticked, then offers red-skills |
red-dev uninstall |
what to remove — and confirms before removing it |
red-dev wsl |
whether to set WSL up on a fresh Windows machine |
Omakub asks these at first run; here they are re-runnable, because the answers change when a project does.
Two things are not questions and never were. The Start Menu hotkeys and the
RedSkills marketplace are part of core, so installing red-dev at all is enough
to get them.
The marketplace is checked per agent, because each one is wired differently and each can arrive later:
| Agent | Wired by | Checked with |
|---|---|---|
| Claude Code | marketplace | claude plugin marketplace list |
| Codex CLI | marketplace | codex plugin marketplace list |
| OpenCode | generated plugins and skills | its uninstall manifest |
Installing Codex a week after Claude is enough to get the marketplace into it — one unwired host is reason enough to run the installer, which configures every host it detects. With no agent installed it does nothing and says so.
plan names the provider that would satisfy each tool, and marks what is
already there:
red-dev plan core[core]
git apt:git (present)
ripgrep apt:ripgrep (present)
fd apt:fd-find (present)
bat apt:bat (present)
starship gh:starship/starship:starship-x86_64-unknown-linux-gnu.tar.gz (present)
zellij gh:zellij-org/zellij:zellij-x86_64-unknown-linux-musl.tar.gz
docker aptrepo:docker-ce,docker-ce-cli,containerd.io,...The wsl scope reads differently on each side of the boundary. From inside the
distro:
[wsl]
wsl-interop builtin:wsl-interop (managed)
nerd-font builtin:nerd-font (managed)
alacritty-host winget:Alacritty.Alacritty (managed)
windows-terminal builtin:windows-terminal (managed)From native Windows, the same scope is entirely skipped — and every skip says why:
[wsl]
wsl-interop skip (native Windows needs no interop shim)
nerd-font skip (the Windows host provides this instead)
alacritty-host skip (the Windows host provides this instead)
windows-terminal skip (the Windows host provides this instead)A skip is a decision. One without a reason is an undocumented gap wearing a decision's clothes, so the manifest cannot express one.
red-dev install wsl --dry-run:: os=linux distro=ubuntu version=24.04 env=wsl arch=x64
:: scope: wsl
would install wsl-interop via builtin:wsl-interop
would install nerd-font via builtin:nerd-font
would install alacritty-host via winget:Alacritty.Alacritty
would install windows-terminal via builtin:windows-terminal
ok dry run — nothing changedEvery provider is idempotent. Re-running after a partial failure is the normal recovery path, not an edge case — one tool failing never aborts the rest:
warn alacritty: ENOEXEC: unknown error, posix_spawn 'cmd.exe'
ok themed: zellij, btop
ok converged — restart your shellA converge inside the fullscreen interface follows the tail, which is right until the moment something fails — at which point the thing you want to read is the line that just scrolled past. So the log scrolls:
↑ ↓ / k j |
a line at a time; moving up stops following the tail |
PgUp PgDn |
a screen at a time |
g / G |
the top; the bottom, which resumes following |
Reaching the bottom re-arms the follow on its own, so there is no mode to
remember. The status line says paused whenever it is off — a log that stops
moving during a live converge otherwise reads as a hang.
Bad input is rejected before any provider runs, which is the cheapest possible place to fail:
$ red-dev plan nonsense
fail invalid scope 'nonsense' (expected: core, desktop, wsl)
$ red-dev frobnicate
fail Unknown command: frobnicate
Available commands: platform, plan, install, update, doctor, theme, menured-dev share # where the root is, and how this side spells it
red-dev share adopt starship # move one tool's configuration into it
red-dev share adopt # what can be sharedA setting applied on one side is then the same setting on the other. The split matters more than the directory does, and each third was measured rather than assumed:
| configuration | shared | 65 ms against 22 ms to read twenty files |
| binaries | co-located, bin/linux and bin/windows |
ELF and PE are different formats |
| source code | never | a build goes from 324 ms to 2726 ms |
So bin/ is split by format and never plain bin — that is the part one
directory cannot deliver. WSL gets both, since interop lets a distro run a
Windows .exe; Windows gets only its own, because it would find files it
cannot run.
The root is stored the one way both environments can agree on — as Windows
spells it — and each side translates. There are three spellings, not two:
C:\Users\me\.reddev for PowerShell, /c/Users/me/.reddev for Git Bash, and
/mnt/c/Users/me/.reddev for WSL.
Nothing moves on its own. adopt copies rather than moves and leaves the
original where it is; on a boundary this fiddly, deleting the file you had been
editing is the wrong kind of tidy.
git is included rather than replaced, and btop stays local — both name
absolute paths that exist on exactly one side.
The command is the same everywhere. What differs is which scopes apply and
which machine the work lands on — red-dev platform will tell you before you
commit to anything.
curl -fsSL https://raw.githubusercontent.com/reddb-io/red-dev/main/boot.sh | shScopes: core + desktop. Everything installs locally; Alacritty and zellij
run natively, the Nerd Font is registered in the local font store, and the
wallpaper goes through gsettings. The desktop scope owns the font on the two
targets that have no Windows host to reach — bare-metal Ubuntu and native
Windows — and it verifies the family resolves before configuring the terminals
that depend on it, rather than trusting the copy to have taken.
Warning
This target is implemented and unproven — no bare-metal Ubuntu has run it.
Start with red-dev install --dry-run.
curl -fsSL https://raw.githubusercontent.com/reddb-io/red-dev/main/boot.sh | shWSL 2 is an explicit invariant, not an assumption. From Windows, red-dev sets
version 2 as the default for every future distro and reads wsl --list --verbose before entering the selected distro. An existing WSL 1 distro is
never used silently: red-dev wsl offers to convert it after warning that the
conversion can take time, while red-dev doctor reports the architecture in
use. From inside a distro, red-dev wsl verifies it and prints the PowerShell
conversion commands when Windows does not report version 2.
Scopes: core + wsl. The core half installs inside the distro; the wsl
half deliberately reaches out to Windows, because that is where the terminal
and the fonts live. It registers the Nerd Font in the Windows font store,
configures Windows Terminal and Alacritty on the host, installs Alacritty via
winget, and re-registers the WSL interop binfmt entry that enabling systemd
silently removes.
That crossing needs interop working. If .exe calls fail with an exec format
error, red-dev install wsl repairs it.
The font goes in for the current user first, which asks nothing of anyone. Some
machines — Entra-joined Windows 11 among them — ignore per-user font
registrations entirely, and the symptom is a terminal that refuses to start with
font "FiraCode Nerd Font Mono" not found while the files and the registry both
look right. So the install does not trust itself: it asks Windows whether the
family resolves, and only when the answer is no does it install machine-wide and
ask for consent. red-dev doctor asks the same question afterwards.
irm https://raw.githubusercontent.com/reddb-io/red-dev/main/boot.ps1 | iexRun it in PowerShell — Windows PowerShell 5.1 or 7, elevated or not. It needs no
administrator: the binary lands in %LOCALAPPDATA%\red-dev\bin and winget
installs per-user where the package allows it.
irm, not curl. In Windows PowerShell 5.1 curl is an alias for
Invoke-WebRequest, which returns a response object rather than the script text;
iex happens to work on it only because ToString() yields the body, and the
alias does not exist at all in PowerShell 7. irm returns a String directly.
And curl.exe … | bash is not an alternative on Windows: bash there resolves
to the WSL launcher, so it would install the Linux build into your distro while
looking like it installed on Windows.
Scopes: core + desktop, everything through winget except the RedDB tools,
which come from their own releases. The shell is Git Bash, not PowerShell —
that is what makes the shipped dotfiles apply, and it is why this project
standardises on bash rather than treating Windows as a separate world.
It hands over to red-dev itself, so you land in the fullscreen interface
rather than in a converge that already started. Choosing Install asks first:
where to share configuration, which shell the terminal opens, which agents,
which runtimes, which optional tools, ble.sh, the font, and the theme — with
the palette previewed while the cursor moves. Previous answers come back
pre-ticked, so agreeing again is enter, enter, enter, and q returns to the
menu rather than starting anything.
A Windows machine running WSL is two machines: separate home directories,
separate PATHs, separate copies of red-dev. Converging one used to say nothing
about the other, and the half you type in is usually the distro — so a Windows
install could report success beside a distro three versions behind, with no
error anywhere and the feature you had just installed simply absent.
The desktop scope now reaches across. It finds the default distro, compares
its red-dev to this one's, installs the matching version inside it when they
differ, and then runs red-dev install core there. That converge is idempotent
and costs seconds on a distro that is already current; the expensive case is the
one that needed the work.
The wsl scope is the same boundary crossed the other way — distro reaching out
to the host for the terminal and the fonts. Whoever is converging owns the
crossing.
Turn it off with RED_DEV_NO_WSL_SYNC=1. red-dev doctor reports the skew
either way.
Two keys, both anchored on Alt, written as Start Menu shortcuts — which is where
they have to be for the key to fire. No AutoHotkey, no PowerToys: a .lnk
carries a hotkey natively, and byte 21 of the file format carries the elevation
flag.
| Key | Opens |
|---|---|
Ctrl+Alt+T |
the terminal — bash inside WSL, through Alacritty when it is installed |
Ctrl+Alt+Shift+T |
PowerShell, elevated — it will prompt for consent |
Ctrl+Shift+T is deliberately not among them. It is reopen-closed-tab in every
browser, in VS Code and in Windows Terminal itself, and a global hotkey beats the
focused application — so claiming it took that away from the whole machine.
A theme switch also sets Windows' dark mode and accent colour, from the same per-theme intent GNOME uses — so "gruvbox is orange" means one thing on both sides. Windows shows the accent on title bars and the taskbar only when colour prevalence is on, so that is set too. Already-open windows keep their old colour until reopened.
Tiling is the one place Windows needs help. Windows 11 has Snap Layouts
natively, and PowerToys —
Microsoft's own — has FancyZones, which is the real analogue of omakub's
tactile, plus Command Palette in place of its Super+Space launcher. It is
offered among the optional tools rather than installed for you, because it is a
program that stays running. It ships with no grid configured; open it once to
draw one.
What Windows genuinely does not offer is switching to a numbered virtual
desktop. omakub binds Super+1..6; Windows has virtual desktops and only
sequential navigation, and reaching a specific one means calling
IVirtualDesktopManager, whose interface changes between Windows builds.
Important
Open a new terminal afterwards. The installer adds
%LOCALAPPDATA%\red-dev\bin to your user PATH, and Windows does not push
that into processes that are already running — so any shell that was open
before will keep reporting red-dev as not found while every other shell finds
it. This is the single most common "it did not install" report, and it is not
an install failure.
Note
boot.ps1 has been syntax-checked and ASCII-gated in CI but has never run on
a clean Windows machine.
Installing on the Windows side and inside WSL is normal and supported — they converge different scopes and share the host's terminal configuration.
One thing they cannot share: Alacritty has no profiles, so it opens exactly one shell. Which one is a recorded choice rather than whichever side converged last:
red-dev shellWindows Terminal has profiles and does not need this; red-dev already sets its
default to the distro, opening in your Linux home rather than under /mnt/c.
red-dev theme gruvbox ok themed: zellij, btop, neovim, vscode, bat, delta, lazygit, opencode, herdr, windows
ok wallpaper set
ok Windows Terminal configured (backup at .../settings.json.red-dev-backup)
ok theme: Gruvbox Dark — open a new terminal to see itTen of them, the same set omakub ships: tokyo-night · catppuccin ·
gruvbox · everforest · kanagawa · matte-black · nord ·
osaka-jade · ristretto · rose-pine.
A theme in Omakub is eight files, not a palette. Colouring only the terminal is what makes a switch feel half-done: the multiplexer keeps its old blue, the editor keeps its old background, and the seams show immediately.
Omakub themes eight surfaces and every one is an application with a window. The command-line tools it installs keep whatever colours they shipped with — so a Kanagawa terminal shows you a Monokai diff and a Dracula file browser. Those are surfaces too, and unlike the desktop ones they work on all five targets:
| surface | how |
|---|---|
| alacritty, zellij, btop, neovim | a generated theme file each |
| VS Code | workbench.colorTheme in a settings file parsed as JSONC — comments and trailing commas survive the edit, and where no exact theme is published (Osaka Jade) it says so rather than picking a lookalike |
bat |
written twice, since Debian renames the binary and the config follows it |
delta |
through git config, where red-dev already made it the pager |
lazygit |
our block only; anything else in the file survives |
opencode, herdr |
told to follow the terminal rather than given a copied palette |
| Windows | dark mode and accent colour |
| GNOME | light/dark preference and accent |
| wallpaper | generated from the palette, not shipped as an image |
Telling an agent to follow the terminal instead of copying sixteen hex values
is omarchy's idea, and it is the better
one: following cannot drift. Where herdr ships a theme with our name — it has
tokyo-night, catppuccin, gruvbox and nord — that wins, because its
author tuned those for its own interface.
Each writer owns a generated file and references your config rather than
rewriting it — your zellij keybindings and the rest of btop.conf are yours.
Wallpapers are generated from the palette, not shipped as photographs: no licensing question, an exact match to the theme, and no download. The PNG encoder is 120 lines with no image library, so it works inside the compiled binary on every target.
When a machine stops matching the manifest, these are the three places to look — in this order.
| Target | What does not work, and why |
|---|---|
| Ubuntu desktop | The desktop scope is implemented and has never run on real hardware. GNOME hotkeys, extensions and dock settings are not ported at all. |
| Ubuntu 26.04 | The u26 manifest column exists and no 26.04 machine has exercised it. Package-name drift is undiscovered. |
| WSL | Windows interop cannot work under sudo -u <other-user>: WSL_INTEROP points at a per-session socket that sudo drops. Real invocations run as you, so this affects test harnesses only. |
| Native Windows | No switching to a numbered virtual desktop — Windows offers only sequential navigation, and reaching a specific one means an interface that changes between builds. No zellij session persistence across reboots. No ble.sh-style line editor. |
| All | red-ui is a desktop app: it installs on Ubuntu desktop and native Windows, never inside WSL, where a Linux GUI has no display to draw on. |
Stated plainly because "implemented" and "known to work" are different claims, and a README that blurs them costs someone an afternoon.
Uncaught exceptions and rejections are written to
%LOCALAPPDATA%\red-dev\crash.log before the process exits, and on Linux to
~/.local/state/red-dev/crash.log. A fullscreen application that dies takes the
console with it, so the stack scrolls past inside a window that is already
closing — the file is the copy that survives.
That file earned its place. Picking Install from the fullscreen interface used
to kill the process and close the console, and three separate experiments failed
to reproduce it: two render() calls in one process, exit() from inside a key
handler, and a wall of output after teardown all survive in a real console. The
crash log named it in one stack — a second render() whose initialisation
failed and whose own cleanup then wrote to a stdout that was already gone. The
interface hosts every view in one render now.
To see the development warnings the shipped binaries suppress:
bun run build:debugNot an environment variable. bun build --compile substitutes
process.env.NODE_ENV at build time, so nothing at runtime — not this program,
not your shell — can reach that check.
For a while this end pointed a provider at a filename nobody published — red-ui's
release carried a .deb, an .AppImage, an .rpm and a web bundle, and no
Windows asset, so red-dev skipped it there with that as the stated reason.
The Windows staging step has since landed upstream: red-ui now publishes
red-ui-windows-x86_64-setup.exe, and the manifest consumes it the same way
red-request does — Linux takes the red-ui_*_amd64.deb, native Windows runs the
setup installer silently with /S, so a clean native-Windows converge gets the
app. The glob keeps the wildcard where the version goes; anchoring it to today's
release is the 404-on-next-release bug this project already learned once.
WSL is still the one place it is not installed, and on purpose: red-ui is a GUI
app, and a Linux GUI inside a distro with no display is the exact mistake the
desktop scope exists to avoid — the Windows target already covers that machine.
Why the code is shaped the way it is, and what it cost to learn.
The orchestrator is TypeScript compiled with bun to a standalone binary, so native Windows needs no bash to bootstrap. The dotfiles stay shell, because your shell sources them, and they travel inside the binary as text imports — the normal way to get red-dev is to download one executable, not to clone a repository.
Omakub shells out to gum for prompts, which means the UI cannot be drawn until gum is installed — precisely why a broken gum download aborts the whole install before showing a single screen. Compiling the interface in removes that bootstrap dependency: red-dev can always draw its own interface, including the screen that reports a failed install.
Porting surfaced real defects in the community WSL forks of Omakub. None are Omakub's fault — they are what happens when a desktop-shaped tool is bent toward WSL. Each became a design rule.
A pinned version behind a latest URL. omakub-wsl pins gum to 0.14.1 but
downloads from /releases/latest/download/gum_0.14.1_amd64.deb. The path
resolves to whatever is newest while the filename still says 0.14.1, so it
404s the moment upstream ships a release — and set -e aborts the whole install.
Here, gh: providers match a glob against the asset names a release actually
publishes, and fail loudly listing the candidates.
PATH replacement kills WSL interop. Upstream does
export PATH="<fixed list>", discarding the ~20 /mnt/c entries WSL injects.
winget.exe, explorer.exe and code.exe stop resolving, which breaks the very
host access the WSL target depends on. config/bash/path.sh
prepends and dedupes instead of replacing.
A prompt that is shipped but never loaded. defaults/bash/rc sources
shell, aliases and init, omitting prompt. The repo looks complete; the
machine gets no prompt.
systemd silently kills WSL interop. systemd-binfmt clears binfmt_misc on
boot and re-registers only what /etc/binfmt.d/ declares — and WSL's own
WSLInterop entry is not there. Every .exe then fails with an exec format
error that points nowhere near the cause. Since red-dev is what enables systemd,
red-dev declares the entry.
Store-installed commands are invisible to stat. winget and friends are
APPEXECLINK reparse points, so an existsSync-based PATH scan reports winget
as absent on a machine that plainly has it. Detection uses where.exe.
The archive's tealdeer cannot fetch its own pages. 24.04 ships 1.6.1, which
points at a cache URL whose format has since changed: every tldr --update fails
and every query answers "Page cache not found". red-dev installs the release
build instead.
| Need | Go to |
|---|---|
| What gets installed, per platform | src/manifest.ts |
| Platform detection and capabilities | src/platform.ts |
| apt, ppa, apt repos, winget, GitHub releases | src/providers.ts |
| Shell configuration, shipped as-is | config/bash/ |
| Terminal and multiplexer config | src/alacritty.ts, config/zellij/ |
| Themes and where they are applied | src/themes.ts, src/theme-apply.ts |
| The WSL-to-Windows boundary | src/wsl.ts |
| Wallpaper generation | src/wallpaper.ts, src/png.ts |
| Bootstrap scripts | boot.sh, boot.ps1 |
git clone git@github.com:reddb-io/red-dev.git
cd red-dev
bun install
bun test # 33 tests over the decision logic
bunx tsc --noEmit
bun run build # both binaries, cross-compiled from one host$ bun run build
[2.2s] compile dist/red-dev-linux-x64
[2.4s] compile dist/red-dev-windows-x64.exe bun-windows-x64The Windows target needs no Windows runner. Two smoke tests gate the release,
because the whole distribution model rests on them: that the TUI and the
embedded dotfiles both survive bun build --compile.
The tests cover the decisions where a wrong answer is silent — which manifest column a release gets, which scopes apply to a platform, whether an asset glob matches the right file, and whether bad input is rejected. Cases come from bugs this project actually hit, not from chasing coverage.
Early, but the loop runs end to end.
- Working:
platform,plan,doctor,menu,install,update,theme,apps,agents,lang,shell,uninstall,ui - Providers: apt, ppa, apt repositories, winget, vendor install scripts, GitHub releases on both Linux and Windows, and builtins for dotfiles, fonts, Alacritty, Windows Terminal and WSL interop
- Verified on WSL Ubuntu 24.04 and native Windows from one source tree, and on a freshly created user for the dotfiles path — including the full install path from the published release
- On native Windows:
tq 0.13.0,reddb 1.23.2anddit 0.3.0installed and running from their own releases, Red Request installed silently without a UAC prompt, PowerToys through winget, and the accent colour read back from the registry as the colour the theme asked for - Configuration shared across the boundary both ways, with the same bytes read
from
/mnt/c/...andC:\... - Nerd Fonts installed and verified — the family is confirmed to resolve before the terminals that need it are configured — on Ubuntu desktop and native Windows, alongside the existing WSL-reaches-Windows font path
See Known limitations for what is implemented but unproven.
irm https://raw.githubusercontent.com/reddb-io/red-dev/main/boot.ps1 | iexThen, in order:
- Open a new terminal. The
PATHentry does not reach shells that were already running, and this is the single most common "it did not install". red-dev— the interface. Install asks its questions first; nothing converges until you answer.- Try
Ctrl+Alt+TandCtrl+Alt+Shift+T. red-dev theme gruvbox, then look at a title bar. Windows applies the accent to windows opened after the switch.red-dev doctor— it should report no drift.
If anything dies, %LOCALAPPDATA%\red-dev\crash.log is the file to send.
MIT.