The same install.sh one-liner that installs on Linux now installs on
macOS, and lerd update self-updates there too. Every dashboard editor moves to
Monaco, and Tinker's intelligence now comes from phpantom_lsp reading the real project, with
inline SQL and per-line results. lerd tui becomes a mouse-driven tabbed dashboard, the
CLI gains a styled feedback layer with responsive tables, and the website moves to its lerd.sh home.
Underneath, a focused security pass closes name-injection, root-write and websocket-rebinding holes.
Recent Homebrew releases block third-party taps until you brew trust them, so the
tap stopped being a frictionless path. lerd's own install.sh is now cross-platform: the
same curl … | bash one-liner installs on macOS the way it already does on Linux. All the heavy
lifting, Podman Machine, mkcert, the /etc/resolver entry and the launchd plists, already lives
in the cross-platform lerd install, so the script just delivers the right binary and hands off.
.bash_profile on macOS (login shell), .bashrc on Linux.
brew stays an alternative, with a brew trust note.# the Linux one-liner, now on macOS too curl -fsSL https://lerd.sh/install.sh | bash # or Homebrew, with a trust step on recent brew brew trust geodro/lerd && brew install geodro/lerd/lerd && lerd install
darwin + arm64 detection, the darwin_<arch> asset, brew-installed prereqs without sudo.
lerd update pulls the darwin archive and reloads launchd agents, deferring to brew upgrade for a Cellar binary.
Boots out the launchd agents by their real com.lerd.* Label and removes detached lerd-* containers, BSD-sed safe.
Every in-browser editor moves from CodeMirror to Monaco, the Tinker REPL, the nginx
overrides, php.ini and service tuning, and the site .env editor, lazy-loaded so the always-on
bundle actually shrinks. Tinker's autocomplete, diagnostics, hover, quick fixes and formatting now come from
phpantom_lsp, a self-contained Rust PHP language server that analyses the real project instead
of static lists, so completions resolve actual models, relationships, scopes and vendor classes.
// "Class not found" → quick fix inserts the use, grouped at the top User::query()->where('active', true)->count(); // ↳ Line 2 · the executed SQL renders as its own card, bindings inlined
One lazy-loaded Monaco wrapper across every editor, Monarch grammars for nginx, ini and dotenv. CodeMirror dropped entirely.
ui · MonacoEditorA Rust PHP language server lerd manages on the host beside fnm and mkcert, bridged per connection over a websocket.
Code-action imports, format on paste, per-line result badges, and every Laravel query as a full-width card.
lerd tui is reshaped around a top tab bar with Dashboard, Sites and Services
screens, switchable by click or ctrl+left/right. Mouse support runs throughout, clicking a tab,
a row, or a dashboard item jumps to its tab with that item selected, and the wheel scrolls whichever pane it
is over without moving the selection. The Dashboard is a six-card overview mirroring the web UI, with
app, service and worker logs surfaced in context.
lerd tui # click a tab, or ctrl+←/→ to switch · Dashboard | Sites | Services # click a site/service/worker on the Dashboard → jumps to its tab, selected # wheel scrolls the pane under the cursor; keys keep the selection on screen
Three clickable top tabs replace the old combined layout, with the version on the far right of the tab row.
internal/tuiClickable rows, tab strips and Debug lenses; wheel scrolls the hovered pane; keyboard selection follows once per move.
The interactive accent becomes the lerd/Laravel red across both the TUI and the new CLI feedback layer.
A new internal/feedback package is the single source of the lerd colour palette
(shared with the TUI) and a set of progress primitives, so every command gives consistent terminal feedback,
a spinner that a check or cross replaces in place, a live accumulating line, aligned key/value
summaries, styled prompts and an amber warning glyph. All of it degrades to plain text when stdout is
piped, redirected, or NO_COLOR is set, so the MCP server and scripts keep parsing stable output.
lerd env keeps a --verbose mode with full per-service detail (what the MCP server invokes); the install/start build TUIs keep their own rendering.lerd env # each service ticks off on its own line as configured lerd sites # renders as a responsive table lerd doctor | cat # no leaked ANSI when piped
The shared palette and progress primitives, with plain-text degradation and several piped-ANSI leaks fixed.
internal/feedbackLinking gets friendlier, running a command in an unlinked project offers to link it
through the init wizard rather than dead-ending, and lerd link routes through that same wizard
and handles non-PHP projects. DNS gains pinnable upstream servers and heals a stale
lan:expose mapping when the host LAN IP changes, postgres-timescaledb joins the presets,
and the website moves to its lerd.sh home with a rebuilt landing page.
cd ~/Code/some-app && lerd artisan migrate # unlinked? offers the wizard lerd link # routes through init; non-PHP handled lerd dns upstream 1.1.1.1 # pin the upstream resolver lerd db:set postgres-timescaledb # new same-family preset
An unlinked project is offered the wizard instead of dead-ending; ensureSiteForCwd resolves and links the same directory.
Choose the upstream servers lerd-dns forwards to rather than only the detected resolvers.
A stale lan:expose mapping is repaired when the host LAN IP changes, with a macOS PF_ROUTE watcher.
A focused security pass closes three issues a hostile linked project, a local user, or a malicious site open in the browser could each exploit, alongside a round of reliability fixes to certs, podman startup, DNS and the MCP surface.
Site and worktree-branch names flowed into site_init shell commands and CREATE DATABASE identifiers. SiteSlug now maps every character outside [a-z0-9_] to an underscore, and database create/drop escape the identifiers as a second layer. #613
The NetworkManager dispatcher rewrote each user's lerd.conf as root, so a symlink could redirect that write onto a root-owned file. The rewrite now runs through runuser as the owning user, with the upstream list filtered to IP/port tokens first. #613
The dashboard handshake accepted any Origin whose host matched Host, which a rebinding page controls. The same-origin fallback now also requires an IP literal or a reserved local TLD, so a public domain rebound to loopback is rejected; loopback, LAN, and .test are unchanged. #613
TLS certs are swapped atomically so an nginx reload never sees a missing cert; worktree DBs are provisioned and realigned on link and env. #608
The machine start is retried once before failing the install, and versioned DB alternates no longer leak the bare canonical service. #605
Worktree, diagnose, service env and check-updates results route through content blocks so the host actually sees them.
AI agent detection is forwarded into the container, and the tray uses a dark icon on light desktop panels.
Legacy framework versions resolve to the lowest available definition instead of failing.