f338f6cb152a1c2df1dcf12e42600b557f7aae6b
1045
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
06a3dbca42 |
Offer to reboot when toggling sudoless Docker; show only the relevant menu entry (#8098)
* Offer to reboot when toggling sudoless Docker; show only the relevant menu entry Group membership only takes effect on a fresh session, and in practice a logout or newgrp isn't enough — only a reboot reliably applies it. So the setup/remove commands now flag the reboot and offer to do it now with a gum confirm (like the GPU toggle), and the notices say "after a reboot" instead of pointing at logout or newgrp. The existing-user migration passes OMARCHY_DEFER_REBOOT so it does not prompt mid-update — omarchy-update-restart still handles the reboot once the whole update finishes. The Setup > Security menu also showed Sudoless Docker under both Setup and Remove. Condition the Setup entry on the group being absent (Remove already conditions on it being present), so only the applicable one appears. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gb7x6poap4hGCndPx5qt5T * Ask omarchy-sudo-docker whether Docker needs sudo Every place that chooses between talking to Docker directly and elevating was testing group membership by hand, and the menu guards tested the wrong thing: they read the running session's groups, which do not change until the reboot, so after enabling sudoless Docker the menu still offered Setup — the one action that could no longer do anything — while Remove stayed hidden. Add omarchy-sudo-docker as the single answer, with the two questions that actually differ in that window. By default it asks whether this session can reach the socket, which is what decides if a command must elevate: lazydocker and the Windows VM keep prompting until the reboot lands. With --configured it asks whether the account is set up for sudoless Docker, which is what the menu and the toggles need, so the menu switches to the action that can change state as soon as the group is written. Also correct a comment: nothing surfaces reboot-required in the bar; it is omarchy-update-restart that reads it during a later update. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gb7x6poap4hGCndPx5qt5T --------- Co-authored-by: David Heinemeier Hansson <david@hey.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
b5ded31e2f |
Don't put the user in the docker group; make it opt-in (#8056)
* Don't put the user in the docker group; make it opt-in The docker group is root-equivalent: anything in it can `docker run -v /:/host` and rewrite the host as root with no password. On a single-user box that's not an escalation (the owner is already a wheel/sudo user), but it hands any code running as the user — a rogue plugin, a poisoned dependency — a silent, headless, passwordless path to root that sudo's password prompt would otherwise gate. Stop granting the docker group by default. The daemon still runs (docker.socket); the Docker TUI and the Windows VM reach it through a polkit prompt, and the plain `docker` CLI runs under sudo. Sudoless Docker is a warned opt-in via Setup > Security (omarchy-setup-security-sudoless-docker). No automatic path may re-grant it: install and first-boot provisioning never record or apply the group (provisioning also filters a docker line left in an older factory snapshot), and the Quattro upgrade no longer adds it. The Windows VM keeps needing the root daemon for a privileged container (KVM, NET_ADMIN), so it is reworked to run without the group and without becoming a new way in: - The compose lives in a root-owned dir and is only written by an elevated, input-validated writer. A root-invoked bring-up must never consume a file a user-process could rewrite to bind-mount / into the guest — the old ~/.config/windows compose was exactly that. Volume paths are rebuilt from $HOME on migration rather than trusted from the (user-writable) legacy file, path validation rejects traversal, and the privileged sub-action is checked against an allowlist before dispatch (a slash in it would otherwise run as a path). - pkexec elevates a verified root-owned command path, not a PATH-resolved one, so an authorized prompt can't be redirected to an attacker's binary. - The guest password is kept in a private 0600 per-user file for RDP instead of a world-readable compose, and a declined authorization is reported as such, never as a completed stop. Existing installs auto-migrate the VM (no redownload) and refresh the stale Docker launcher entry. 🤖 Generated by Opus 4.8 in Claude Code. Reviewed by Codex XHigh. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Co-Authored-By: Codex XHigh <codex@openai.com> Claude-Session: https://claude.ai/code/session_01Gb7x6poap4hGCndPx5qt5T * Migrate existing installs off the docker group The default flip only reaches new installs; existing users keep their docker group membership and stay exposed. Extend the migration that already refreshes the Docker launcher to also remove the current user from the group when present, reusing omarchy-remove-security-sudoless-docker so there is one source of truth for the change and its notice. It takes effect at next login (the current session keeps working), and passwordless docker can be turned back on from Setup > Security > Sudoless Docker. Migrations run with sudo available — during `omarchy update`, or in the terminal the pending-migrations notification opens — so the privileged removal does not prompt at an unattended login. The no-op path (already out of the group) needs no privilege. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gb7x6poap4hGCndPx5qt5T * Refuse symlinked VM mount sources; correct the docker CLI docs Review follow-ups. valid_path keeps a traversal string (/./, //, ..) out of the compose, but it is a string check: a symlink planted at ~/.windows or ~/Windows redirects the privileged bind mount exactly as traversal would, because docker follows it. So verify the mount sources as root immediately before bringing the VM up — refuse a source that is a symlink or resolves through one — which is where the string check cannot help. A missing source stays fine (docker creates a plain dir). Also correct the development-tools manual: the CLI is not transparently elevated (there is no docker wrapper and `d` is still plain docker), so say plainly that docker on the command line takes `sudo` until sudoless Docker is enabled. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gb7x6poap4hGCndPx5qt5T --------- Co-authored-by: David Heinemeier Hansson <david@hey.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Codex XHigh <codex@openai.com> |
||
|
|
5afc9e1495 |
Revert "Require signed packages from the Omarchy repository"
Reverts
|
||
|
|
39cffb8f51 | Require signed packages from the Omarchy repository | ||
|
|
535d8f3485 |
Regenerate mise wrappers that still print mise's output to stdout (#8041)
omarchy-mise-install gained --quiet on its `mise use -g` line so a wrapper no longer prints mise's "tools: pkg@version" ahead of the tool's own output. That only changes wrappers written from then on, and the migration that installed the current ones is already marked complete, so every wrapper already on disk keeps polluting stdout: `claude --version` still answers with two lines, and a wrapper for a protocol-speaking command answers with a line its caller cannot parse. Rewrite them through omarchy-mise-install so the template stays in one place. Wrappers were written by four different generated forms over time and only the ones a later migration happened to regenerate moved forward, so all four are recognized. The whole file has to match one of them exactly, which leaves a wrapper someone has added a line to alone and makes a second run a no-op. Claude-Session: https://claude.ai/code/session_01Mv1FyKG2VGZRdtknQ7YjU8 Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
ef6d9e6605 |
Stop an installed theme from running code (#7884)
* Stop an installed theme from shipping code `omarchy theme install <url>` clones a stranger's git repository into ~/.config/omarchy/themes, and omarchy-theme-set then copied that whole directory into the staged theme. Most of the files in a staged theme are code rather than colour: Hyprland requires hyprland.lua and gum_env.lua from it at login, Neovim loads neovim.lua at startup, and alacritty.toml, kitty.conf, foot.ini and ghostty.conf each name the program the terminal launches. Installing a theme was the same act as running its author's code, and nothing on disk distinguishes an installed theme from one the user wrote. Stage only what a theme needs in order to be a theme: colors.toml, light.mode, the preview and unlock images, and image files under backgrounds/. Everything else is ignored, named on stderr, and generated from default/themed/*.tpl instead. Symlinks are never followed, because in an untrusted theme they point wherever the author chose. A theme older than colors.toml keeps its palette: its alacritty.toml is read for colours in a scratch directory and only the resulting colors.toml is staged, so the terminal config never lands. The filter belongs in omarchy-theme-set rather than in omarchy-theme-install because staging is the choke point. It also covers themes installed before this change, themes copied in by hand, and files a theme gains later through `omarchy theme update`. First-party themes under $OMARCHY_PATH/themes are unaffected. Per-theme overrides of a generated file are no longer available to user themes; the template at ~/.config/omarchy/themed/<file>.tpl replaces that, and icons.theme is the one setting with no replacement. 🤖 Generated by Opus 5 in Claude Code. * Stop a theme URL or name being read as an option or a path Three paths in the theme commands took an attacker-shaped string straight into git, into basename, or into rm. `git clone "$REPO_URL"` passes the URL as the first positional argument, so a URL beginning with a dash is parsed as an option instead and the destination path becomes what git tries to clone. Pass `--` before the URL so a URL is always a URL. git also treats `<helper>::<address>` as a remote helper to run; git's own protocol.allow default already refuses `ext::`, so rejecting that shape here is a second line rather than the fix, and it keeps holding if that default ever moves. The helper name is a bare word at the very start of the URL, which is what the guard matches: an scp-style IPv6 host such as git@[2001:db8::1]:org/repo.git carries `::` of its own and still clones. `basename "$REPO_PATH" .git` has the same problem one step later, after the scp-style prefix has been stripped: `host:-s/foo.git` leaves basename reading `-s` as an option and returning `.git` as the theme name. Take the name with `--`. That name is then joined into a path that is about to be `rm -rf`'d, so a repo whose basename came out as `..` would take ~/.config/omarchy with it. omarchy-theme-remove had the same shape from its own argument, and omarchy-theme-set's sed/tr normalization does not stop a name containing a slash. Reject empty, anything starting with a dot, and anything containing `/` in all three, before the name reaches a path. 🤖 Generated by Opus 5 in Claude Code. Reviewed by Codex XHigh. Co-Authored-By: Codex XHigh <codex@openai.com> * Re-stage the current theme for installs that already applied one Dropping a theme's code at staging time only takes effect the next time a theme is staged. An install that already applied an extra theme keeps that theme's hyprland.lua, gum_env.lua, neovim.lua and terminal configs in ~/.local/state/omarchy/current/theme, which Hyprland requires at login and the terminals include at launch, and nothing forces a theme change — so for those installs the fix would arrive whenever the user next happened to switch themes, which may be never. Re-stage once through omarchy-theme-refresh. First-party themes stage identically, so the cost for everyone else is a single retint during an update they are already running. 🤖 Generated by Opus 5 in Claude Code. Reviewed by Codex XHigh. Co-Authored-By: Codex XHigh <codex@openai.com> * Stop a theme's unlock image republishing a file it points at omarchy-plymouth-set-by-theme reads unlock.png straight out of ~/.config/omarchy/themes, which is an installed theme's own directory and outside the staging filter, and hands the path to omarchy-plymouth-set. That path was copied twice into world-readable /usr/share — once by the user into the Plymouth theme, and once by `sudo cp` into the SDDM theme. A symlink there was followed both times, so a theme could name a file it cannot read and have root publish it. Refuse a symlinked logo, and copy the staged logo to SDDM instead of rereading the caller's path as root. The staged copy is made by the user, so nothing privileged opens a path the caller chose. 🤖 Generated by Opus 5 in Claude Code. Reviewed by Codex XHigh. Co-Authored-By: Codex XHigh <codex@openai.com> * Limit only what an installed theme could run Two corrections to the rule this branch introduced, both narrowing it to what it was actually for. It applied to every theme under ~/.config/omarchy/themes, which swept up themes the user wrote themselves. Their machine, their file: a theme they wrote is theirs to fill however they like, and Omarchy's own themes were never in scope. Only a theme that came from someone else needs limiting, and the repo already knows which those are — omarchy-theme-extras calls a theme with a `.git` directory an extra and a symlink someone's working copy, because that is what `omarchy theme install` leaves behind when it clones. Use the same test. It was also an allowlist, which dropped files that carry nothing but colour and left theme authors worse off for no gain. Drop only what can run: any `*.lua`, since Hyprland requires a theme's hyprland.lua and gum_env.lua at login and Neovim loads neovim.lua at startup; the four terminal configs, since each names the program the terminal launches; and vscode.json, whose extension field reaches `code --install-extension` and a VS Code extension is arbitrary JavaScript. Everything else an installed theme ships is kept, so btop.theme, chromium.theme, helix.toml, icons.theme, keyboard.rgb and shell.toml go back to being the theme's to set. Symlinks are still dropped, now at any depth rather than only where an allowlist happened to look. A denylist is wrong the moment someone adds a template and does not think about it, so the decision is forced rather than remembered: the test fails on any default/themed/*.tpl whose output is recorded as neither code nor colour, and a new terminal or a new Lua-loading editor cannot be added without classifying it. What this does not cover, and is written down in docs/theming.md rather than implied: a theme shipped as an archive and unpacked by hand looks exactly like one the user wrote. `omarchy theme install` only takes git URLs, so the supported path is always filtered, but this marks where a theme came from and is not a sandbox. 🤖 Generated by Opus 5 in Claude Code. * Fix what the review found Four things, all confirmed against the source before changing anything. The migration failed permanently when the active theme had been removed. `omarchy theme remove` deletes the directory without repointing theme.name, so the name survives, the staged copy survives, and omarchy-theme-refresh exits 1 because neither source directory exists — leaving the migration pending forever and the stale staged Lua exactly where it was, which is the one thing it existed to remove. Seed the default theme in that case: there is nothing to re-stage from, and the removal should have left a working theme behind anyway. The staging test skipped the strict-mode header that docs/testing.md makes the contract for every shell test. Adding it means the patterns that fail on purpose have to stop being bare `cmd && fail` compounds, which errexit reads as the script itself failing; the mutations were re-run afterwards to confirm the assertions still fire rather than the run dying early and looking like something else. The guards in omarchy-theme-install and omarchy-theme-remove had no coverage — they were checked by hand and left that way. theme-install-guards-test.sh stubs git and the themes directory and proves an option-shaped URL, a transport helper, and a name that would climb out all stop before git or rm runs, that a dash inside the path no longer becomes a basename option, and that an ordinary URL still clones and applies. The new docs/theming.md prose was hard-wrapped, which AGENTS.md forbids for docs/. Unwrapped. The rest of that file is wrapped from before and is left alone rather than churned through this change. 🤖 Generated by Opus 5 in Claude Code. Reviewed by Codex XHigh and Copilot. --------- Co-authored-by: Codex XHigh <codex@openai.com> |
||
|
|
1cc5c72e2c |
Add Ori as a lazy-loaded agent and a default-agent choice (#7709)
Ori is OpenRouter's harness: `ori claude`, `ori codex` and `ori opencode` start those agents against OpenRouter's model catalogue, and `ori code` is Ori's own agent. That last one is what the default-agent entry launches, bare — Ori has no approval prompt to skip, so there is no "don't stop to ask" flag to pass it the way the other agents get one. The package is `github:OpenRouterLabs/ori-releases`, because upstream ships prebuilt binaries as release assets and publishes nothing to npm. mise's `github` backend picks the right asset per platform and verifies GitHub's artifact attestations on the way in; `ubi` resolves the same release but is deprecated for removal in mise 2027.1. The menu glyph at U+E909 is OpenRouter's own mark. Ori publishes no logo of its own and its product page renders that one, so there was no Ori-specific mark to prefer over it. Co-authored-by: Codex XHigh <noreply@openai.com> |
||
|
|
2c593dbbaa |
Switch back to the packaged quickshell now that 0.3.1 kills synchronously (#7769)
* Switch back to the packaged quickshell now that 0.3.1 kills synchronously Omarchy shipped the quickshell-git build for a single fix: 0.3.0's `kill` returned before the instance had exited, so the kill loop in omarchy-restart-shell could race a dying shell. Upstream 0.3.1 ships that fix, which makes extra/quickshell the better package to be on again — signed, versioned, and not rebuilt from a moving branch on every update. The migration swaps unconditionally instead of first checking which version the mirror offers. A machine left holding quickshell-git while the shipped package list names quickshell has no way to reconcile the two: omarchy-reinstall-pkgs installs that list with --needed, which does not skip a name that is not installed, and the conflict it then walks into has no answer under --noconfirm. A mirror that is briefly behind installs 0.3.0 instead and the next upgrade carries it to 0.3.1, which is much the cheaper way to be wrong. 🤖 Generated by Opus 5 in Claude Code. Reviewed by Codex XHigh. Co-Authored-By: Codex XHigh <codex@openai.com> * Drop the quickshell version note from the shell restart loop The comment qualified the kill loop as needing 0.3.1 or newer, but omarchy-restart-shell ships in the same package upgrade that brings quickshell along, so a machine running this code already has the version the loop depends on. The caveat could never be false where it was read, which left it as version archaeology rather than something the code could not say for itself. 🤖 Generated by Opus 5 in Claude Code. --------- Co-authored-by: Codex XHigh <codex@openai.com> |
||
|
|
ed7bae4ac5 |
Replace Gemini coding agent with Antigravity (#6900)
* Replace Gemini coding agent with Antigravity * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Remove the dead Gemini mise wrapper in the Antigravity migration Remove Preinstalls no longer lists gemini, so the wrapper Omarchy created would have stayed in ~/.local/bin with nothing left to clean it up. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Install Antigravity when it is the default a Gemini user is migrated onto The opt-out check skipped the install but the rewrite ran anyway, so anyone who had removed the preinstalls was left with a default agent naming a command that is not there. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Fix Antigravity skill provisioning and Gemini wrapper migration - Wires Omarchy's default skills into Antigravity by linking them to ~/.gemini/config/skills/ in bin/omarchy-provision-user and migrations/1786719479.sh. - Fixes the Gemini wrapper migration in migrations/1786719479.sh to recognize and remove wrappers containing either `mise use -g "gemini"` or `mise use -g --quiet "gemini"`, while leaving hand-written wrappers intact. - Adds regression tests for both skill provisioning and wrapper removal in test/shell.d/default-agent-test.sh and test/shell.d/provision-user-test.sh. * Stop the provisioning test from retheming the session it runs in The test ran the real omarchy-provision-user, which sources install/user/all.sh and so reached omarchy-theme-set: hyprctl reload against the live compositor, gsettings against the live desktop, and a global Node install, none of which the skill symlinks it asserts need. Its mocks for omarchy-done and omarchy-refresh-applications were shadowed anyway, because provisioning prepends $OMARCHY_PATH/bin ahead of them, so stubbing the install suite at its own path is what a mock cannot do here. The exit status is checked rather than discarded: the assertion held even when provisioning died outright, because the symlinks are made twenty lines before the suite runs. * Match the Gemini default and wrapper the way Omarchy writes them The migration decided both questions differently from the code that owns them. It read the default agent with grep -qxF, while omarchy-default-agent takes the first line through read, so a padded " gemini " that the launcher still resolves was left naming an agent the launcher no longer supports. The wrapper it deletes was matched anywhere in the file, so a hand-written one that only mentions the installer's line in a comment went with Omarchy's own. Reading it the launcher's way and anchoring the match settles both against whoever wrote the file. The skills loop guards its glob the way migrations/1786539345.sh does, so an empty source cannot leave a symlink named "*" behind a migration already marked complete. Co-Authored-By: Codex XHigh <noreply@anthropic.com> * List Antigravity among the skill directories The manual named Claude Code, Codex, Pi and the generic location; provisioning now links ~/.gemini/config/skills too. Co-Authored-By: Codex XHigh <noreply@anthropic.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: David Heinemeier Hansson <david@hey.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Omabot <omabot@omarchy.org> |
||
|
|
cf29ce1867 | Add hey (hey-cli) as a lazy-installed mise tool (#7626) | ||
|
|
a4219f8f4a |
Store theme backgrounds as webp (#7477)
* Decode webp in the shell The background and the lock screen are drawn by Quickshell, so they decode through Qt, which ships handlers for png, jpeg and gif but not webp. QImageReader answers "Unsupported image format" and the layer comes up blank. Any third-party theme shipping a .webp background hits this today, even though every path that goes looking for a background already globs the extension. qt6-imageformats supplies the missing plugin for 71 KB downloaded. Its one new dependency of substance, libwebp, is already on every machine by way of libvips. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Store theme backgrounds as webp WebP codes both of the things these backgrounds are made of better than the formats they were in: the photographs, where its lossy mode is worth a third or more over JPEG at matched quality, and the flat art and dot patterns, where its lossless mode undercuts an oxipng-packed PNG. 28 of them become lossless webp and decode bit-for-bit identically (AE=0), so the dot patterns and flat-shaded pieces carry no quality question at all. That includes 0-launch, whose alpha channel comes through intact. The other 51 are photographs held to the same 38 dB PSNR floor as the JPEG pass, landing between 38.0 and 54.6 dB. Every image keeps its exact pixel dimensions, for 29.8 MB. Each one is encoded from the original as it stands in quattro rather than from the file the earlier commits produced, so nothing picks up a second generation of loss on the way here. 13 stay JPEG. WebP is plainly larger for most of them, and three are grainy enough that its filter smooths the grain instead of coding it: PSNR plateaus near 34 dB however high the quality goes, well under the floor. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
262d6f0681 |
Switch to the mise-bin package (#7244)
* Switch to the mise-bin package mise-bin carries mise's own release artifacts from the Omarchy repo -- PGO+BOLT-optimized on x86_64, glibc-native on both arches -- instead of Arch's mise, and tracks jdx/mise releases directly. Existing installs need a migration because the two packages conflict, and omarchy-pkg-add cannot make the swap: pacman answers its own conflict question with No under --noconfirm and fails the transaction. --ask=4 answers that one question, so mise-bin replaces mise in a single transaction -- which is also what keeps omarchy-zsh and omarchy-fish, both of which depend on mise, satisfied through the swap by its provides. * Guard the swap with a conditional instead of an early exit Two-path control flow takes an if, per the style guide; the early exit only made the swap line unreachable from a distance. |
||
|
|
1c3da94906 |
Keep mise wrappers from writing to stdout (#6940)
mise use -g announces the resolved tool on stdout, so every wrapped command prepended a "tools:" line to its own output. That corrupts anything speaking a protocol over stdout, such as codex app-server. Pass --quiet, which keeps errors on stderr and preserves the exit status. The obsolete-wrapper check in the agent migration matched the generated command verbatim, so loosen it to match the package instead of the flags. Closes #6908 Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
b5bb8dac05 |
Repair foot.ini configs holding a literal \n[text-bindings] line (#6938)
A 3.8.3 migration appended the section header with one backslash too many, so sed wrote the literal characters instead of a newline plus the header. foot rejects the line and stops reading the rest of the file. The later text-binding migration matches the header with grep -qxF, misses the broken line, and appends a second section, leaving the config broken across the Quattro upgrade. Closes #6903 Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
d35f4b6d59 |
Wait on the profile being repaired, not on every browser (#6837)
The migration asked a user to close every running browser before repairing the Copy URL shortcut, but a browser only ever rewrites its own Preferences on exit. Waiting on all browsers deadlocks `omarchy update` for anyone whose main browser is effectively never closed: the pending ghosts commonly sit in a stale profile nobody has open, yet the migration blocks on the always-open browser until the prompt is declined, failing the whole update. A running Chromium-family browser holds a SingletonLock (and socket) inside its user-data-dir, so whether the profile being repaired is open is mechanical. Gate on that instead of on the sheer presence of a browser process — the repair proceeds where the affected profile is closed, and browsers attached to other profiles no longer hold the update hostage. The gate stays conservative while an affected profile actually is open, and the existing post-repair verification still catches a browser that starts mid-repair and restores stale Preferences on exit. The migration test now simulates an open profile with its SingletonLock instead of a pgrep stub; every prior scenario still passes. |
||
|
|
df708831b6 |
Keep the Copy URL migration's browser prompt visible (#6842)
gum draws its confirm UI on stderr, so the migration's `2>/dev/null` threw away the whole prompt while gum still held the terminal in raw mode reading keys. With a browser open, an update stopped after "Running migration (1786643346)" on an unpainted screen with no way to tell it was waiting for an answer. Nothing else in the repo suppresses gum's stderr; the redirect only ever hid gum's own error in the no-terminal case, where the migration already explains itself on stderr before deferring. Fixes #6841 Claude-Session: https://claude.ai/code/session_01So4yKzTDdJJp7orjnF8i1J Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
de854d3f0c |
Free the Copy URL shortcut from ghost extension registrations (#6821)
* Rebind ghost Copy URL shortcut registrations to the pinned id Chromium never hands a suggested shortcut to one extension while another — even a long-gone one — still holds the registration. Profiles that first loaded Copy URL before its id was pinned registered Alt+Shift+L under an id derived from the extension's load path at the time, so the pinned extension never receives the shortcut and the keypress does nothing (#6816). The quattro upgrade tried to repair this against one hardcoded path-derived id, which only ever matched a single home directory. The historical ids are unknowable in general — they hash long-gone absolute paths through whatever symlinks existed then — but the registration itself names the command, so a migration now rebinds any copy-url command that points away from the pinned id, unless that id belongs to an extension that is actually installed or the pinned extension already holds a binding of its own. Browsers rewrite Preferences on exit, which reverts any repair made while one runs, so the migration asks for this user's browser windows to be closed first — failing and staying pending when there is no terminal to ask in or the prompt is declined. The backup a repair leaves behind marks it as attempted but unverified: until a browser-free run confirms the registration stayed repaired, the migration keeps itself pending rather than trusting a disk state an open browser may still overwrite. The upgrade-time repair is dropped: the upgrade already runs migrations, so the migration is the single implementation. Fixes #6816 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Pin the WhatsApp Slim extension id Keyless unpacked extensions get path-derived ids, which go stale if the load path or packaging ever changes — the same class of bug that broke the Copy URL shortcut for pre-package installs. Pin the id with a manifest key like the other bundled extensions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
ecd57bceee |
Drop the kms hook when the proprietary NVIDIA driver handles early KMS (#6791)
* Drop the kms hook when the proprietary NVIDIA driver handles early KMS install/hardware/nvidia.sh early-loads nvidia_drm (modeset=1) for early KMS, but HOOKS still carried the kms hook, so autodetect pulled nouveau and ~100 MB of its GSP firmware into every initramfs for a driver that never runs. On a Limine UKI setup that meant a 256 MB image where ~144 MB is normal, doubled again by the fallback history on /boot. Filter kms out of HOOKS when nvidia_drm is in MODULES (nvidia.conf sorts before this drop-in) and every PCI display controller is NVIDIA. Hybrid systems keep kms so the iGPU retains early KMS at the LUKS prompt. Verified on an RTX 4090 (nvidia-open-dkms 610.57.04): UKI shrinks 256,183,296 -> 144,066,048 bytes, nouveau and its firmware gone, the nvidia-utils GSP blobs and all four nvidia modules retained, Plymouth still owns the LUKS prompt via nvidia_drm. Fixes #6790 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Address review: quote literals, harden PCI detection, add shell tests Quote fixed string literals in the [[ ]] comparisons per AGENTS.md, and read the PCI tree through OMARCHY_PCI_DEVICES_PATH, the same seam bin/omarchy-hw-nvidia already uses. Require a positively identified NVIDIA display controller before dropping kms: an empty or unreadable PCI tree previously counted as "no non-NVIDIA GPU" and would have dropped the hook. Unexpected trees now keep kms. Cover the conditional in test/shell.d/nvidia-kms-hook-test.sh: nvidia-only, hybrid, no nvidia_drm, MODULES unset under set -u, audio-function-only, empty tree, and a device directory missing its sysfs attributes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Treat unreadable PCI devices as inconclusive and make the test hermetic A device whose class/vendor attributes cannot be read could be another GPU, so skipping it let a readable NVIDIA GPU beside it drop kms without having verified the whole tree. Count it as a non-NVIDIA sighting so kms stays, and cover the mixed case in the test. The test also sourced the host's /etc/vconsole.conf under set -u, where a valid KEYMAP-only file makes the XKBLAYOUT expansion fail in the subshell and ties the result to the machine running it. Predefine XKBLAYOUT and FILES before sourcing the config. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Rebuild existing initramfses once the kms hook no longer applies The settings package deploys the new omarchy_hooks.conf conditional, but nothing rebuilds the initramfs when only a mkinitcpio drop-in changes, so existing NVIDIA-only installs would carry nouveau's ~100 MB of GSP firmware until their next kernel update. Following the precedent of 1784476564, add a migration that rebuilds via limine-mkinitcpio — once per machine, and only where evaluating the installed drop-ins shows the conditional actually dropped kms, so hybrid machines, non-NVIDIA machines, and user-edited configs are left alone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Document the mid-sourcing MODULES caveat in the kms conditional A later-sorting drop-in that resets MODULES outright (as surface_device_modules.conf does) would strip nvidia_drm after kms was already dropped. Every machine Omarchy writes such a file for is hybrid Intel and keeps kms through the PCI scan, but that is worth stating so the invariant is not broken by accident. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: David Heinemeier Hansson <david@hey.com> |
||
|
|
6fad76184f |
Unmask wpa_supplicant left masked from the iwd era (#6793)
Quattro hands wifi to NetworkManager, which starts wpa_supplicant through D-Bus activation. Installs carrying a wpa_supplicant.service mask from the iwd days break that activation: NetworkManager retries five times, gives up, and every wifi device sits at "unavailable" with no network to research the fix on. Remove the mask (including a runtime one) and restart an active NetworkManager when a wifi device is stuck, so wifi comes back without a reboot. Fixes #6783 Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
2cc3510d2a |
Offer an AI diagnosis when a process crashes (#6746)
* Offer an AI diagnosis when a process crashes systemd-coredump journals every core dump under a known MESSAGE_ID with the crashing program, pid, and signal as structured fields. omarchy-crash-watch follows that stream and raises a "Process crashed: <program>" toast; clicking it opens omarchy-agent-crash, which briefs the default agent on the crash. The toast goes through omarchy-notification-send --exec rather than a libnotify action, because the shell runs clicks from its own omarchy-exec hint and never emits ActionInvoked. It keeps the default "omarchy-action" app name too, the only one shouldBypassDnd() lets through -- a crash being the last notification worth swallowing. It stays quiet until an agent is configured, since a diagnosis is all it offers. The method lives in a diagnose-crash skill rather than the prompt, so it is edited in one place and works with whichever agent is default. It covers investigating the core, and reporting a confirmed Omarchy bug upstream: scoped to bugs Omarchy controls, searched for duplicates first, only with the user's agreement, and signed with the model and harness that produced it. A migration reaches existing installs, whose skill symlinks and unit enablement would otherwise sit behind one-time setup paths. * Let the diagnosis clean up the core it extracted "Do not modify or delete anything" contradicted the symbolization step right above it, which writes a core to a temp file and deletes it on exit. Read literally, the core survives -- and the same section warns it holds passwords and tokens. The prohibition is about the system, not about your own scratch. * Do not spend a crash toast on a dead notification server The shell owns org.freedesktop.Notifications, so its own crash takes the notification server down with it -- and a shell crash is exactly what you want told about. The toast was sent once into that gap and the dedupe window was recorded regardless, so the rest of the crash loop went quiet for a minute and `journalctl -n 0` never replays what was missed. It now waits for the restarted shell to reclaim the bus name, as omarchy-migrate-notify already does, and only a delivered toast starts the dedupe window. |
||
|
|
9502b81f3b |
Reshape the agent launcher into omarchy agent (#6757)
* Reshape the agent launcher into omarchy agent omarchy-launch-agent becomes omarchy-agent, with prompts on omarchy-agent-prompt rather than the bare route: `omarchy agent` is both a command and a group, so a positional prompt there would shadow any subcommand under it. The launcher takes flags only and points at `omarchy agent prompt` when handed one. Every agent window now launches under a fixed org.omarchy.agent app-id instead of omarchy-launch-tui's default of org.omarchy.<binary>, so one rule floats them all whichever agent is default. Omarchy also stops picking an agent for you. omarchy-default-agent prints nothing until one is chosen, leaving every entry under Setup > Defaults > Agent unchecked, and a first-run invitation offers to take you there. * Wordsmith * Cover the agent routes and the invitation The route split is the point of the change, so exercise `omarchy agent`, `omarchy agent prompt`, and a rejected positional prompt through the router rather than only the binaries behind them. The invitation gets the same treatment as the Voxtype and fingerprint ones: it notifies once, opens the agent defaults menu, and leaves both the notification and the marker alone for anyone who already chose an agent. * Offer the agent choice from the keybinding Super + Shift + Ctrl + A now runs `omarchy-agent --pick`, which opens Setup > Defaults > Agent when nothing is chosen yet. A keypress that writes to stderr and opens nothing just looks broken. * Reach existing installs with the agent invitation first-run installs the invitation hook, and existing accounts marked it complete long ago, so they would never see it -- while being the accounts most likely to need it, since the old getter returned opencode implicitly and most have no agent recorded at all. Post-update hooks run later in the same update, so the invitation arrives without waiting for another one. * Say what the Defaults submenus set Setup > Defaults lists Agent, Browser, Terminal, Editor, but the header inside each repeated the same bare word, which reads as a category rather than a setting -- and says nothing at all when the menu is summoned straight into it. The list keeps its short labels; the headers now name the setting. |
||
|
|
ab57ad65fd |
Make notification history the last ten notifications on disk
History was a pair of in-memory lists mirrored into notifications.json, split into "pending" and "past" by a seen/unseen distinction no surface exposed, capped at 100, deduped by an id that repeats across server generations, and pruned by a 15-minute TTL. Replaying it showed five rows drawn from whichever list happened to hold them. Every toast already writes a file under ~/.local/state/omarchy/notifications so it can survive a shell restart. That file is now the history record: when the popup leaves the screen it moves into notifications/history instead of being deleted, the newest ten are kept, and showHistory replays exactly what is in there, including the toasts still on screen when it is asked for. A notification DND silenced is written straight into the same directory, since a toast that never showed is the one worth looking back at. That leaves the models, notifications.json history payload, past pruning, and the /tmp image cache that existed to keep century-old history thumbnails alive with nothing to do, so they go. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
2fa490dc96 |
Rebuild boot images the Plymouth migration left stale
1784917531 gated its UKI rebuild on initramfs_async=0 being present in the Limine config, but omarchy-settings ships omarchy-defaults.conf with that parameter already in it. Any machine that installed the package and ran the migration in the same update matched the config the package had just written, skipped the rebuild, and kept booting an image baked before the config existed — without initramfs_async=0, so encrypted boots still fell back to an unthemed text LUKS prompt. Compare the booted command line against the configured one and rebuild when they disagree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
b99fd91cf1 |
Simplify the Quattro upgrade, and stop it leaving an error bar behind (#6716)
* Accept either name for the lock authentication command
omarchy-setup-lock was renamed to omarchy-apply-lock in
|
||
|
|
1bb6600c76 |
Repair theme symlinks the state-move migration left dangling (#6717)
* Repair theme symlinks the state-move migration left dangling 1781043107.sh re-linked legacy theme symlinks whose targets were stored with a literal "~/.config/omarchy/current/..." string. The replacement used the same literal tilde, which the filesystem never expands inside a symlink target, so btop, Helix, and VS Code/Cursor lost their theme and the migration reported success anyway. Fix the source migration to relink through the already-defined $current_state_dir variable, and add a follow-up migration that repairs the links the applied version left dangling — matching the existing 1785002349.sh pattern, so it is idempotent and leaves custom links alone. Co-Authored-By: Claude <noreply@anthropic.com> * Only repair theme symlinks that could never have worked The repair matched any target containing omarchy/current, so a working link into a user's own dotfiles was rewritten to the state directory and their setup was lost. Claim a link only when its target starts with a literal "~/", which the filesystem never expands, and names this exact theme file: that is what 1781043107.sh wrote, and no working link can look like it. A dangling target is not enough on its own, since a dotfiles repo may just be unmounted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Clean up every scratch directory the CLI suite creates Five of the eight mktemp directories were never registered with the exit trap, so each run left them behind in /tmp. Route them all through a helper that records them for cleanup. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Drop the theme symlink migration tests A migration runs once on each machine and is then inert, but a test for it sits in the suite forever. Now that the repair behaves correctly, keep the migration and let it go untested rather than grow the suite permanently. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: David Heinemeier Hansson <david@hey.com> |
||
|
|
4d0531f351 |
Add QR code capture (#6705)
Select a screen region and decode the QR code in it to the clipboard, so an otpauth:// setup code shown on screen no longer needs a phone. The decoded value is only ever placed on the clipboard, and marked sensitive so clipboard history skips it. Decoding is restricted to QR so a stray barcode elsewhere on screen can't take the clipboard instead. Co-authored-by: Hlib Kanunnikov <hlibwondertan@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
186668a70f |
Apply the Broadcom Wi-Fi quirk to Macs without a T2 (#6652)
* Apply the Broadcom Wi-Fi quirk to Macs without a T2 brcmfmac lets the Wi-Fi firmware run the WPA handshake itself, and on Apple hardware that offload fails against an access point in WPA2/WPA3 transition mode: the client associates, the four-way handshake never completes, and NetworkManager reports the password as wrong. feature_disable=0x82000 turns off the firmware supplicant and authenticator so wpa_supplicant does the handshake in software. That quirk already shipped, but only for Macs with a T2 chip. The bug is in the Broadcom firmware rather than in the T2 bridge, so it was never the right thing to gate on: a MacBookPro11,4 has BCM43602 with 2015 firmware, fails exactly this way, and got nothing. Gate on the hardware that actually has the firmware — an Apple machine with a Broadcom wireless part — which covers both. Moving it out of fix-t2.sh also leaves one owner for the file. Two leaves writing the same config would have meant the later one silently winning, decided by an ordering in all.sh nobody would think to check. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Gate the Broadcom Wi-Fi quirk on the T2 ID or a brcmfmac chip ID Sniffing lspci for an Apple vendor with a Broadcom network controller made T2 Macs depend on a detection line they never needed: they carry a T2 PCI ID that is always there, and the class name half of `lspci -nn` comes from the pci.ids database. Keep their original gate untouched. Naming the rest by DMI model does not hold up either, because the model year does not predict the part. A MacBookPro11,4 from Mid 2015 carries a BCM43602 and needs this; a MacBookAir7,2 from Early 2015 carries a BCM4360 and does not. Covering the lineup by name takes around twenty identifiers across four product lines and grows every time Apple ships hardware. The set has an exact definition already: the PCI IDs brcmfmac binds, from the driver's own brcm_hw_ids.h. That reaches the 2016 and 2017 MacBook Pros and the T2-less iMac19,1 and iMac19,2 that a hand-written list missed, and it leaves out the BCM4360 Macs for free, since their out-of-tree wl driver would never read a brcmfmac option anyway. Matching an exact vendor:device ID also drops the piped `grep -q`, which returns 141 under pipefail once the producer is killed by SIGPIPE (#6608). The test runs the leaf with pipefail so the chatty lspci stub proves it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Fix Macs already installed without the Broadcom Wi-Fi quirk The quirk is written at install time, so a machine set up before it shipped never gets it, and no pre-T2 Mac ever did. Those installs still fail the WPA four-way handshake against an access point in WPA2/WPA3 transition mode, which is the state the reporter had to repair by hand. Appending leaves anything else in the config alone: modprobe reads every options line for a module, and nothing else sets feature_disable. Only an active options line counts as already applied, and the driver keeps the old behaviour until it reloads, so this asks for a reboot rather than pulling brcmfmac out from under a connection that currently works. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: David Heinemeier Hansson <david@hey.com> |
||
|
|
e4604fbcfb |
Generate image picker thumbnails with libvips in parallel (#6686)
Thumbnails were generated one ImageMagick process at a time. Queue the missing ones and drain them across every core with vipsthumbnail, which decodes and encodes faster and lets the per-process startup overlap. Cold cache for a 92 wallpaper directory drops from 14.2s to 1.3s, and the bundled theme previews from 2.0s to 0.26s. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c53190be07 |
Remember Bluetooth on/off through the rfkill soft block (#6682)
* Turn Bluetooth off with an rfkill soft block BlueZ never persists an adapter's Powered property, so turning Bluetooth off in the panel lasted only until the next boot. Omarchy's answer was AutoEnable=false, which persists nothing either — it just means "never power the adapter on", so Bluetooth came up off every boot whatever the user had chosen. The soft block already does the job. systemd-rfkill saves every switch under /var/lib/systemd/rfkill and restores it early on the next boot; that is the entire purpose of the unit. Blocking also covers every controller at once, where bluetoothctl only ever addresses the default one. So the block becomes the state and BlueZ follows it: with AutoEnable back at its stock default, lifting the block is enough for bluetoothd to power the adapter up on its own. Powered still tracks the block, so the panel switch and icon read it exactly as before. Everything that turns Bluetooth on or off goes through omarchy-bluetooth-power, because bluetoothctl power on fails while a block is set. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Carry installed machines over to the rfkill block Existing installs have AutoEnable=false, so their adapter is down at every boot and Powered is the only record of what the user actually wants. Read it before anything changes, hand it to the block, then put AutoEnable back to its default so bluetoothd can act on that block. Only the exact line Omarchy wrote is reverted, so a hand-edited opt-out survives. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Ask the power helper for a direction, not a toggle The helper runs detached and the switch only moves once BlueZ catches up, so a second click inside that window re-read the pre-click state and undid the first. The panel already knows which way it wants to go, so let it say. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Read every controller and bound the power-up wait The block hits every Bluetooth radio at once, but the state was read from a bare bluetoothctl show, which reports the default controller only. A powered dongle sitting behind a powered-down internal controller read as off and got blocked along with it. Enumerate the controllers and take any powered one as on, exposed as is-on so callers do not each reinvent the read. The wait counted probes rather than time, so a wedged D-Bus turned a two-second bound into roughly fifty across a full power-up. One deadline around the whole wait holds it near nine. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Change the radio through sudo in the migration /dev/rfkill is only writable unelevated from an active graphical seat, so an update run over SSH failed here with EACCES. Migrations run under bash -e, so that aborted before the config revert and the marker, and aborted again on every retry. The privilege guidance already calls for sudo on machine-wide work run from a visible terminal. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
4e31b61af0 |
Swap terminaltexteffects for ttfx (#6670)
* Swap terminaltexteffects for ttfx ttfx is a Rust port of terminaltexteffects that renders byte-identical frames as a single dependency-free binary. Same option names, defaults, and exit codes, so every invocation here is unchanged apart from the command name. The screensaver runs at --frame-rate 120 with --random-effect. On a fullscreen canvas Python cannot hold that for the heavier effects (beams: 14.1 ms/frame against an 8.3 ms budget, so ~71fps); ttfx renders the same effect at 564fps. Startup drops from ~107 ms to ~1 ms, and the base image no longer needs Python for the screensaver. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Need a migration --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
e1d0c4e0a8 |
Ship the keyboard layout widget on the bar and make clicking it work (#6659)
* Hide the keyboard layout widget on a single-layout install There is nothing to read or switch when only one layout is configured, so the label is noise on the bar most people have. Hide it until the keyboard reports more than one, and keep showing it on a Hyprland that doesn't report the list at all rather than hiding the widget everywhere. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Put the keyboard layout widget on the bar by default The widget hides itself unless the active keyboard has more than one layout, so shipping it costs a single-layout machine nothing and saves everyone else from finding it in the plugin list. Sit it just right of the clock, and add it to existing bars the way the agents widget was added, leaving a curated bar and a disabled widget alone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Cycle the layout with the hyprctl command that exists switchxkblayout is a hyprctl command, not a dispatcher, so sending it over the dispatch socket only produced a Lua syntax error and clicking the widget did nothing. Run it instead, against the keyboard the label was read from. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Add an idempotent bar add command Nothing put a widget on the bar without going through the running shell: plugin enable and bar move both forward to it over IPC, which a migration cannot rely on. Add writes the config file the way position and transparent already do, and leaves a widget that is already on the bar where the user put it, so callers can ask for it repeatedly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Put the keyboard layout widget on bars through the bar CLI The hand-written jq was a normalizer, a presence check and a splice for what is now one command that carries all three. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Keep bar add from writing a bar the shell was not reading The shell takes a user shell.json only when it parses, says version 1, and carries a bar layout, and does not deep-merge; anything else leaves the shipped defaults on screen. Reading and writing the user file regardless turned a config holding nothing but an idle timeout into a bar holding nothing but the new widget, and made an unparsable one abort the migration chain on every update. Work against whichever layout is actually in effect, seeding the defaults before placing a widget they do not already carry. A malformed hand-installed manifest fails the whole plugin catalog, which was enough to refuse a first-party widget, so treat an unreadable catalog as no answer rather than a no. Leave a widget listed in disabledPlugins off the bar instead of writing a layout entry the registry refuses to load, and re-check presence inside the mutation so two adds cannot both miss it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Read a widget's default bar section in one place cmd_defaults spelled out the same "defaultSection, or center when it is missing or not a section" rule that the add path already asks for by name. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Rename bar add to bar put 'omarchy plugin add' installs a plugin and 'omarchy bar add' placed one that was already installed, which is too much meaning for one verb. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Place a newly added bar widget with bar put plugin add reached the bar through plugin enable, which forwards to the running shell, so it first had to poll until the shell noticed the clone and then failed outright when no shell was there to ask. Putting a widget on the bar is a config edit, so do that directly and leave plugin enable to the plugins that need registering rather than placing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Put bar widgets through the shell instead of the config file Placing a widget existed twice: once in PluginRegistry, which the shell uses and owns the config it holds in memory, and once as jq against shell.json. The second was there so migrations could run without a shell, which they do not need to: the Quattro upgrade hands over the shipped shell.json before it runs any, and every other path runs inside a session with a shell up. Ask the shell, and say so and carry on when there is none to ask. putBarWidget enables only what is not already on the bar, which is what a caller that cannot know whether it ran before needs, and is the one thing the existing enable path would not do. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
6ddc39520d |
Clean up the terminal and reconnect when SSH connections drop (#6661)
* Clean up the terminal and reconnect when SSH connections drop A remote tmux, herdr, or editor arms terminal modes over the SSH pipe (mouse tracking, focus reporting, the alternate screen) that only it can disarm. When the connection dies instead of exiting cleanly, those modes stay armed on the local terminal, and every mouse move floods the prompt with escape-sequence junk. Wrap ssh in a shell function that disarms those modes after every exit, and automatically reconnects when an established interactive session drops. Remote commands, configured RemoteCommands, and redirected stdin never reconnect, so their side effects cannot replay, and the retry loop runs in a subshell so Ctrl-C cancels both the in-flight attempt and the loop. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Detect dead SSH connections within a minute Without keepalives, ssh does not notice a dead peer until TCP gives up, which can take hours of sitting on a hung terminal with remote-armed terminal modes stuck on. Ship a client keepalive default so drops are detected in about 45 seconds, letting the shell's ssh wrapper clean up and reconnect. ~/.ssh/config is read first and wins, so per-host overrides still apply. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Fail closed when ssh -G cannot resolve the effective config An unresolvable configuration could hide a RemoteCommand, so treat it as non-interactive rather than reconnectable. Also strengthen the tests from Copilot review: assert the complete disarm sequence, and verify on a real interactive pty that Ctrl-C during a retry attempt kills the reconnect loop itself, not just the in-flight attempt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Tolerate the explicit RemoteCommand none when probing ssh -G The literal "none" is how ssh_config cancels a configured RemoteCommand, and some OpenSSH versions emit it even when unset, which would have silently disabled reconnecting entirely. Treat it as no remote command while still failing closed on real ones and unresolvable configs, and make the fake ssh -G emit the "none" form so the behavior tests cover it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
dd61d4a75b |
Ship herdr alongside tmux (#6406)
* Ship herdr with a config that mirrors our tmux setup Installs herdr through the mise shim, ships the matching config as an Omarchy default, and adds the usual refresh/restart pair. The keybindings map tmux sessions to workspaces, windows to tabs, and keep both the prefix and direct bindings from config/tmux/tmux.conf. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Add herdr versions of the tmux dev layout functions hdl, hds, hdlm, and hsl drive herdr through its socket API instead of tmux. hsl tiles into a real grid since herdr has no select-layout tiled. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Namespace the herdr layout helpers so they stay out of the shell Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Create hdlm's tabs in its own workspace instead of the focused one herdr tab create follows the focused workspace without --workspace, so switching workspaces while hdlm loops scatters the new tabs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Lay hsl's grid out in visual order Splitting the first column repeatedly inserted each new column between it and the previous one, so uneven counts put the spare row in a middle column instead of the last. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Report herdr config reload failures instead of swallowing them Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Hide herdr's pane scrollbars to match tmux Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Escape queued herdr layout commands * Install herdr from the omarchy-herdr package instead of mise * Use native herdr resize keybindings for tmux-style pane resizing * Rename the omarchy-herdr package to herdr --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
b925431025 |
Give SSH commands the user-level tool paths (#6632)
* Give SSH commands the user-level tool paths
ssh host cmd runs neither a login nor an interactive shell, so on Arch it
gets the bare sshd PATH and can't find mise-managed tools like the agent
CLIs herdr scans for. Set PATH in the PAM environment (per-user via
@{HOME}), append the user-level dirs in env-bootstrap so login shells and
the uwsm session get them too, and source env-bootstrap before bashrc's
interactive guard for bash variants that read it non-interactively.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Don't let an empty PATH turn into a cwd entry
Appending with a bare "$PATH:" prefix leaves a leading colon when PATH
is unset, which shells treat as the current directory.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
ab367b9477 |
Stop mise wrappers from waiting out the release cooldown
The wrappers install and upgrade their tool on first run, so the cooldown held new versions back for days. Refresh the stubs on existing installs too, since the generator change alone only reaches fresh installs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
81ecac4a60 |
Fix WhatsApp Slim migration targeting brave-origin-beta instead of brave-origin (#6482)
* Fix WhatsApp Slim migration targeting brave-origin-beta instead of brave-origin * Add migration to append WhatsApp Slim to Brave Origin for existing installs * Don't corrupt flags files that lack a trailing newline Appending --load-extension= with echo assumed the file ended in a newline. A hand-edited flags file without one got the flag concatenated onto the previous line, losing both that flag and the extension. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: akashgagda <akashgagda@users.noreply.github.com> Co-authored-by: David Heinemeier Hansson <david@hey.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
b85ae70ebd |
Stop pipefail from turning grep -q SIGPIPE exits into false negatives (#6614)
* Stop pipefail from turning grep -q SIGPIPE exits into false negatives grep -q exits at the first match, and when the producer is still writing it dies with SIGPIPE. Under pipefail that 141 becomes the pipeline's status, so hardware checks like lspci | grep -q read as "not found" on exactly the machines they target. The T2 defaults migration hit this and silently skipped real T2 Macs (#6608). Redirect grep to /dev/null instead of -q wherever a pipeline feeds grep in a pipefail context, so grep reads all input and the producer never gets killed. The install-time T2 checks aren't run under pipefail today but are switched too, since they're the same detection line the issue calls out. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Re-run the T2 defaults migration its broken hardware check skipped The SIGPIPE bug marked 1785944594 as applied without doing anything on affected T2 Macs. The original migration is idempotent, so a fresh migration can just source it now that the guard is fixed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Address Copilot review: fix OCR grep pipeline and prove the T2 repair screen_contains piped tesseract into grep -Fqi under the acceptance suite's pipefail, the same SIGPIPE false negative the rest of the branch fixes. The T2 test's lspci stub now keeps writing past the pipe buffer after the match so every scenario exercises the SIGPIPE case, and a new case runs the rerun migration against fixtures a bitten install would have. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
bb8d2f2cb3 |
Split agent usage into data files and rename the plugin to omarchy.agents (#6603)
* Add agent usage collectors that write display-ready data files
One omarchy-agent-usage-scan-<agent> collector per AI coding agent prints a
complete display-ready usage record — identity, tier, status, rate limits,
and today/week/all-time stats. omarchy-agent-usage-update runs every
collector it finds and writes the records atomically to
~/.local/state/omarchy/agents/usage/, so anything that displays usage only
ever reads JSON from there.
The Claude collector absorbs what the shell previously did in-process:
transcript scanning, the stats-cache/history fallback, credentials parsing,
and the OAuth limits probe, now with a probe throttle and last-good limits
kept across network failures. The Codex collector is the existing scanner
reshaped to the shared record contract.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Redo the model-usage plugin as omarchy.agents watching usage data files
The panel is now strictly a display. It discovers the JSON records that
omarchy-agent-usage-update maintains under
~/.local/state/omarchy/agents/usage/, watches them for changes, and draws
whatever appears — so adding an agent means shipping a collector, never
touching the panel. Marks resolve by convention (assets/<id>.svg with an
optional -light twin), the limits meters read a generic limits array, and
the per-provider QML adapters and in-plugin scanner scripts are gone.
Cross-device sync aggregation stays in the shell and keeps the snapshot
field names older versions wrote, so mixed-version fleets still merge in
both directions.
With the provider fan-out gone, the widget takes its real name: the plugin
id becomes omarchy.agents. A migration renames it wherever a user's config
mentions it — layout entries keep their settings and position, a disabled
widget stays disabled — then primes the data files once and drops the old
scanner cache. The migration test also drops a stale assertion that expected
migrations to restart the shell themselves, which
|
||
|
|
c3bd4a86ae |
Fix unbound XKBLAYOUT under set -u in the keyboard-layout migration (#6539)
* Fix unbound XKBLAYOUT under set -u in the keyboard-layout migration /etc/vconsole.conf only guarantees KEYMAP -- XKBLAYOUT is written by some installers but not required, and vconsole.conf on a stock install may not define it at all. omarchy-migrate runs every migration with set -euo pipefail, so referencing the unset variable directly aborted the migration run instead of just skipping a layout this migration doesn't care about. Read it with a default-empty expansion first, then apply the existing comma-strip separately -- keeps both failure modes (unset, and set with a trailing keymap variant) handled explicitly instead of folding them into one expansion that only covers one of the two. * Survive a missing vconsole.conf in the keyboard-layout migration Defaulting XKBLAYOUT fixed the unset variable but not the other way this line takes the migration chain down. `.` fails when /etc/vconsole.conf is not there at all, `&&` short-circuits, and the non-zero status leaves the command substitution and kills the assignment under omarchy-migrate's `bash -euo pipefail` -- the same abort, one branch over. The file is optional enough that both other readers of it, omarchy_hooks.conf and 1781485962.sh, guard with `-f` first. Run the echo unconditionally so the substitution reports its status instead of the source's. Read both paths from the environment, the way the zram migration already does, and cover the layout cases plus both crashes with a test. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Read the keyboard layout from vconsole.conf alone Two ways the layout could come from somewhere other than the file, both found reviewing the fix before it: Sourcing does not clear an exported XKBLAYOUT, so on a machine that exports one, a vconsole.conf that sets no layout -- or none at all -- left the caller's environment deciding what the initramfs bundles. Unset it in the subshell so only the file can answer. Skipping the source when the file is missing, rather than letting the substitution swallow the failure, also stops depending on errexit being discarded inside command substitution. inherit_errexit takes that back and the chain aborts again; nothing in Omarchy sets it today, but the other two readers of vconsole.conf already check `-f` first and this now matches. Test the layout list past its one Cyrillic entry, both orders of a comma-separated pair, an exported XKBLAYOUT, and inherit_errexit. Each of those catches a mutation that survived before. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Drop the test scaffolding from the keyboard-layout migration A migration runs once and then gets deleted, so a suite entry for this one is upkeep with a short shelf life. The path overrides existed only so that test could aim the migration at fixtures; with the test gone they are indirection nothing exercises, so both go back to literal paths. The fixes stay: check the file before sourcing it, unset XKBLAYOUT so an exported one cannot answer for a file that sets none, and strip the keymap variant before matching. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Toni Nowak <t.nowak@ai-flow.no> Co-authored-by: David Heinemeier Hansson <david@hey.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
6ee243cc37 |
Split the end-user omarchy skill into topic guides (#6602)
* Split the end-user omarchy skill into topic guides Move default/omarchy-skill to default/agents/skills/omarchy and break the monolithic SKILL.md into on-demand topic files for Hyprland config, shell plugins, theming, and hooks. Update the skill symlink wiring, relink existing installs through a migration, and correct claims that had drifted from the implementation: plugin hot-reload, terminal reload, menu customization, refresh scopes, theme overlays, background locations, hook timing, and the packaged (not git-managed) system directory. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Add capture and contributing guides to the omarchy skill Cover screenshots, screen recording, OCR text capture, and LocalSend or Taildrop sharing, plus how to route bug reports, suggestions, and support questions upstream with diagnostics and captures of the problem attached. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Scope Hyprland reload guidance to the Lua config files hyprsunset.conf and xdph.conf are read by separate processes, so hyprctl neither applies nor validates them. Document restarting hyprsunset after editing its config, including in the night light example. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
9cb3640c9f |
Fix T2 Mac suspend and fan defaults (#6562)
* Fix T2 Mac suspend and fan defaults * Avoid repeated T2 boot image rebuilds * Harden T2 migration test matching |
||
|
|
ce93c31af6 |
Merge branch 'quattro' into add-default-agent
# Conflicts: # migrations/1785633225.sh |
||
|
|
c992cdff10 |
Restart the shell unconditionally after every update
Updates routinely replace the shell's QML, and a stale process can lazy-load new files into old code. Restarting at the end of every omarchy update removes the need for migrations to restart the shell or defer one with the restart-shell-required marker: the login-time migration path already runs a fresh shell that hot-reloads shell.json. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
9ca5f63f86 |
Speed up mouse scrolling in foot
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
7b4fb0ad6b | Fix default agent setup edge cases | ||
|
|
1f8819318d | Switch to Omacalc | ||
|
|
a9b84cea8e | Refine default agent menu | ||
|
|
a0e2501e15 | Add configurable default coding agent | ||
|
|
84e87ce67e |
Add oh-my-pi (omp) as a lazy-installed mise tool
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
2cff67fa45 |
Fix sleep lock session environment race (#6493)
Found by @tobi. |