Commit Graph
1356 Commits
Author SHA1 Message Date
9d0849f717 Add the Claude desktop app to Install > AI
Follows the ChatGPT flow: the Install > AI entry runs
omarchy-install-ai-claude in a floating terminal, which installs the
claude-desktop package (Anthropic's Linux desktop beta, repacked from
their Debian repo in omarchy-pkgs) and opens the app. Remove > AI
drops the package along with ~/.config/Claude and ~/.cache/Claude,
the Electron directories the desktop app owns, while keeping
~/.claude, ~/.claude.json, and ~/.cache/claude-cli-nodejs: those
belong to the Claude Code CLI, which ships in its own package and
survives this removal, just as the ChatGPT remover keeps the Codex
CLI.

The menu mark is a new U+E90E glyph in the Omarchy icon font, from
Simple Icons' Claude mark, so it reaches desktops through the next
omarchy-settings release.

Co-Authored-By: Fable 5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
2026-09-09 22:01:35 -05:00
Spencer Bull 1cb960ac43 Use the Hermes desktop portrait for menu icons 2026-09-08 15:02:17 -05:00
David Heinemeier HanssonandClaude Fable 5.1 7e8feb047d Relay the Elgato Cam Link 4K as a 16:9 virtual camera (#10809)
* Relay the Elgato Cam Link 4K as a 16:9 virtual camera

Browser meeting apps such as Zoom's web client ask the Cam Link for a
standard-definition stream, and Chromium settles on the smallest mode it
offers, 640x480. The Cam Link fills that 4:3 frame by cropping its 16:9
input, and the app then paints the frame into a 16:9 tile, so everyone
comes out stretched wide. The web client has no HD switch to avoid it.

Hide the raw capture node from users and re-expose it through v4l2-relayd
as a 1280x720 virtual camera with the same name, so there is still just
one "Cam Link 4K" to pick and no way to negotiate 4:3 from it. udev
starts the relay whenever the Cam Link enumerates and stops it on unplug,
and the relay only pulls frames while something is watching. The sink
runs unsynced because v4l2src stamps each frame with its capture time,
which a synced sink treats as already late and drops.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Take the review fixes for the Cam Link 4K relay

Tie only the device's stop into the relay instance. A start dependency on
it left a job waiting on a device that never comes whenever the base
v4l2-relayd.service is started without a Cam Link attached, since the
package generator wants every configured instance.

Let the loopback unit rerun on each relay start, so a deleted or unloaded
device is recreated on replug instead of the oneshot staying satisfied.

Start the relay outright at the end of the migration. The udev trigger
only starts it when the rule is new to the device, and a failed module
build would otherwise pass silently with the raw camera already hidden.

Run the hardware fix after the Panther Lake kernel swap, as it pulls in a
DKMS module that would otherwise build twice.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 14:09:51 +02:00
David Heinemeier HanssonandClaude Opus 5 a7486beb60 Add Super + Ctrl + Alt + F to toggle a full screen desktop (#10672)
Hiding the top bar and removing the window gaps are the two things you
do to give the screen entirely to your windows, and doing both took two
hands and two hotkeys. `omarchy toggle fullscreen desktop` does them
together.

It only leaves full screen when both halves are in it, so hitting the
hotkey with just the bar hidden (or just the gaps gone) pulls the other
half into line instead of flipping the one you already set.


Claude-Session: https://claude.ai/code/session_01JB9phxP56gnP7qSidkkUJE

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 19:39:27 +02:00
Ryan Hughes 79138c0e70 Follow the active theme in T3 Code
T3 Code reads themes an environment publishes into its state directory, so
a generated t3code.json carries the palette the same way claude.json and
vscode-theme.json already do, and omarchy-theme-set-t3code republishes it
on every theme change for the app to pick up live.

The template overrides only the roles a theme actually designs -- terminal
and code surfaces, text on canvas, borders, selection -- and leaves muted
text, placeholders, and status foregrounds to T3 Code, which contrast-solves
them against whatever canvas it is given. Overriding those directly cost
readability on light themes: White fell to 1.0:1 on placeholder text.

Installing from the menu now routes through omarchy-install-ai-t3-code so a
fresh install publishes the current palette and opens wearing it.
2026-09-07 12:53:10 -04:00
Spencer Bull 9fb73b0051 Hide the upstream Hermes launcher in Omarchy
Keep the package launcher visible when Hermes builds register a second desktop entry. Use the existing launcher hide list so in-app updates cannot restore the duplicate menu row.
2026-09-07 04:20:13 -05:00
Ryan Hughes 04b0a47c9b Configure locate through the packaged service
Reported-by: uiop / @wasdhjklxyz <uiop@wasdhjkl.xyz>
2026-09-06 21:42:31 -04:00
Ryan Hughes c82a0837b0 Merge pull request #10425 from acrogenesis/security/root-owned-sleep-hooks
Harden ownership of installed sleep hooks
2026-09-06 19:36:07 -04:00
Ryan Hughes 0c20243214 Bound GPU mode requests during sleep 2026-09-06 19:34:36 -04:00
acrogenesis feb0557e1d Harden ownership of installed sleep hooks
Publish privileged sleep-hook and hybrid-GPU files through root-owned replacement inodes, repair unsafe existing copies while preserving administrator customizations, and keep partial hibernation setup retryable.

Reported-by: Roger Piñol <rogerpicar@gmail.com>
2026-09-06 14:36:27 -06:00
0d223fe820 Add Muse Code as a default coding agent (#9915)
* Add Muse Code as a default coding agent

Meta ships Muse Code only as a binary, so it installs from the AUR
(muse-code-bin) instead of mise, and a fresh install runs the muse login
browser flow in the install terminal before the agent opens.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0148qKzr366p2Ubu2igCLvPg

* Refine Muse Code menu and prompt forwarding

* Install Muse Code from OPR

* Install Muse Code through mise's HTTP backend

* Preinstall the Muse mise stub

* Use the shared Muse installation flow

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: David Heinemeier Hansson <david@hey.com>
2026-09-06 22:29:16 +02:00
Ryan Hughes 1d466c4003 Add o.rebind for replacing Hyprland keybindings 2026-09-06 16:01:50 -04:00
steelcityapplianceandClaude Fable 5.1 a9eaf7978e Point the shipped agent skills at omacom/omarchy (#10532)
The repository moved from basecamp/omarchy to omacom/omarchy, but the skills
Omarchy installs into ~/.claude/skills, ~/.codex/skills and ~/.agents/skills
still name the old owner. Most gh calls follow the rename, so nothing looks
wrong in normal use. Search does not: `gh issue list --repo basecamp/omarchy
--search ...` exits 0 with no output, and the raw search API answers
"Validation Failed". An agent following contributing.md or reporting.md runs
its duplicate check before filing, sees zero matches, and files a duplicate.

Replace the owner in contributing.md, reporting.md and the issue-template
contact link, which is the same stale name on the contributor path. The git
clone and release URLs in bin/ and manual/ are left alone: GitHub redirects
those, and channel-test.sh asserts the exact clone URL, so changing them is a
separate change.

Fixes #10118

Written by Claude Fable 5.1 via Claude Code, reviewed by Marc Morriss

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 21:29:36 +02:00
a62e34ea8e Add Cursor CLI as a coding agent choice (#10465)
* Add Cursor CLI as a coding agent choice

* Launch Cursor CLI through its agent subcommand with --trust

Cursor CLI still dispatches a one-word prompt that names one of its
subcommands (update, login, help) even after a bare --, so name the agent
subcommand outright and pass the prompt behind -- there, where it also
keeps a prompt starting with a dash from being read as an option. --yolo
only auto-allows commands; the workspace trust dialog is skipped only by
--trust, and a launcher that must not stop to ask needs both.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Leave an official Cursor CLI install alone

Cursor's own installer symlinks ~/.local/bin/cursor-agent, the same path the
mise wrapper takes. The migration now installs the wrapper only when no
cursor-agent command exists, and Remove Preinstalls deletes the path only
when it holds the wrapper omarchy-mise-install wrote, the way the Hermes
wrapper is handled. Selecting the agent treats an executable at that path
other than the wrapper as the user's own install and skips mise, since the
mise shims precede ~/.local/bin on PATH and a mise copy would only shadow
it. The wrapper resolves through mise's registry, which lists cursor-agent
from 2026.8.15 on.

The tests write a real cursor-agent stub before Remove Preinstalls runs,
cover the preinstall opt-out for the new migration, and check that a
symlinked official install survives removal and selection alike while a
dead file at the same path still installs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Give Cursor its brand mark and one name in the menu

Add Cursor's mark to the Omarchy icon font as U+E90D and point the agent
entry and both editor entries at it, so one brand is drawn one way across
the menu. Label the agent entry "Cursor CLI", the name the command and the
manual already use, and spell it the same in the migration and the tests.
Append the manual row after the others at the standard width.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Keep an official Cursor CLI install through a user re-provision

User setup writes every mise wrapper unconditionally, which is fine on a
fresh install but replaces the symlink Cursor's own installer leaves at the
same path when omarchy-provision-user runs again with --force. Guard that
one line the way the migration does.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: John Cavanaugh <59479+cavanaug@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 17:07:40 +02:00
Spencer Bull 6baae0f556 Make Hermes follow the Omarchy theme as a skin
Hermes Desktop installed under Install > AI kept its own palette while every other agent app retinted with the theme. Hermes' skin is its one theme unit for the desktop app, the TUI and the CLI, and its gateway watches the active skin file and broadcasts changes to every surface, so Omarchy publishes a skin named omarchy from a template on every theme switch and nothing Omarchy-specific goes upstream.

Activation goes through hermes config set, which writes the active profile's config and touches the skin so a running gateway repaints at once, and it only replaces Hermes' default skin so a choice made in Hermes stays. A theme switch runs that activation too when the desktop package is present and Hermes is still on its default, so a hand-over the installer missed is finished by the next switch; once the config names the skin a switch never starts Hermes. The desktop adopts a skin from a change broadcast rather than from the config it finds at connect time, and its first launch builds the runtime over minutes, so the installer starts --wait as a transient user unit that outlives the install terminal, activates once the runtime marker appears, republishes after the gateway is up, and reports to the journal. A migration hands the skin to existing Hermes Desktop installs through --activate, which also renders the skin for a theme applied before the template existed.

The generated file is validated before it is published, because Hermes parses it as YAML: only the name, a plain description and #rrggbb colours pass, so an unresolved palette key or a cloned theme's own hermes.yaml leaves the previous skin in place.

🤖 Generated by Fable 5.1 in Claude Code. Reviewed by Fable 5.1 code-review at high.
2026-09-05 23:05:10 -05:00
fa60a8b9bf Add the Perplexity desktop app to Install > AI
Follows the T3 Code / Grok Bot flow: the Install > AI entry runs
omarchy-install-and-launch, so picking it installs the perplexity
package on demand and launches the app when the install finishes.
Remove > AI drops the package along with the app's own config, flags
file, and rpc-server runtime cache, keeping the perplexity-* caches
that belong to Perplexity's other products. Like the Hermes remover,
it sets -u so an unset HOME is a refusal rather than rm -rf paths
rooted at /.

The menu mark is a new U+E90B glyph in the Omarchy icon font, so it
reaches desktops through the next omarchy-settings release.

Co-Authored-By: Fable 5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
2026-09-05 09:48:03 -05:00
Spencer Bull 5345673988 Add OpenClaw to Install > AI as a web app on its own gateway
OpenClaw's desktop experience on Linux is its Control UI, served by the
gateway the openclaw package runs, so the Install > AI entry installs
the package and a web app launcher that routes through the new
omarchy-launch-openclaw: first launch hands off to OpenClaw's own
onboarding wizard, later launches start the gateway when needed and open
the dashboard's single-use browser handoff URL as an app window.
Remove > AI tears the gateway service down through OpenClaw's own
gateway uninstall (falling back to systemctl by hand), aborts rather
than dropping the package under a gateway that will not stop, and keeps
the user's agent in ~/.openclaw.

OpenClaw also joins Setup > Defaults > Agent through the same
agent_installer seam Hermes carries: its CLI is the pacman package
rather than a mise tool, so omarchy-install-openclaw-cli answers
--check/--now with pacman, and omarchy-agent runs `openclaw chat`,
seeding prompts through --message.

The menu mark is a new U+E90C glyph traced from the package's lobster
favicon; E90B stays free for the Perplexity mark still in flight on its
own branch.
The launcher recovers the gateway through `openclaw gateway install --force`
(unit not enabled: missing, or an install that died after writing it) or
`openclaw gateway start` (enabled but stopped), never `openclaw dashboard
--yes`: as of OpenClaw 2026.9.1 that defers to "the owning supervisor" in
both cases, and once the gateway is up it copies a one-time browser pairing
URL into the clipboard. The dashboard probe is bounded so an app-grid launch
cannot hang without a terminal to interrupt it. Removal treats only
systemd's own "inactive"/"failed" as a stopped gateway, so an unreachable
user manager aborts instead of dropping the package under a live process.
All of it verified against a real 2026.9.1 install.
Removal also takes down the node-host unit if OpenClaw ever installed one, and
asks (default no, only on a terminal) whether ~/.openclaw should go too, with
its size: the chats and credentials live there next to hundreds of megabytes
of plugin runtimes and cache OpenClaw downloads for itself.
Onboarding goes through omarchy-openclaw-onboard rather than bare `openclaw
onboard`: as of 2026.9.1 the bare command is the guided flow, which ends by
running a foreground gateway and handing off to a browser tab without
returning, so the install script never reached the app launch and no service
was installed. The helper runs the classic wizard (--flow quickstart
--install-daemon --skip-ui) as a background job that keeps the terminal as its
stdin, so its prompts render and take input as upstream draws them, and stops
it once the gateway answers: upstream leaves the wizard running after its
outro (only the TUI branch exits, and the model sign-in holds a socket open).
Every quickstart prompt precedes the service install, so that point is safe.
A gateway that never comes up after this run applies setup ends the wait as a
failure instead
of hanging, an already-running OpenClaw is left alone rather than mistaken for
this run's success, a gateway answering on the port is only this run's once its
process is the unit's own MainPID (an orphan from an
earlier run) is not mistaken for the service this run installs, and a signal at
the helper takes the wizard down with it.
2026-09-05 09:18:27 -05:00
David Heinemeier HanssonandClaude Fable 5 493067741e Give foot its own touchpad scroll factor (#9793)
foot only applies scrollback.multiplier to discrete wheel clicks, so
precise touchpad scrolling ignores it and crawls at the group's 1.5
factor. Split foot out at 2.0 to match how the other terminals feel.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-09-04 17:59:32 +02:00
Spencer Bull b71dcad96e Merge pull request #7469 from omacom/hermes-agent
Add Hermes as a desktop app and a coding agent
2026-09-01 11:11:57 -05:00
David Heinemeier Hansson 4d017913d0 Update the tagline to Beautiful, Fun & Agentic (#9584)
Matches omarchy.org, the X header, and the ISO. The README and the agent skill carry their lowercase variants.
2026-09-01 11:54:15 +02:00
Ryan Hughes 884ca49340 Point the rc channel at the rc package repository
pacman-rc.conf shipped with [omarchy] on pkgs.omarchy.org/edge — a
leftover from when release candidates published there. Candidates now
publish to a dedicated rc channel, so a machine switched to rc with
omarchy-refresh-pacman was pairing the rc Arch mirror with edge omarchy
packages, and omarchy-version-channel could not name the rc repository
at all (an rc install reported 'rc / unknown').

Point the conf at pkgs.omarchy.org/rc, teach omarchy-version-channel
the rc repository, and repoint existing rc-channel machines with a
migration. The migration only rewrites the shipped pairing (rc mirror +
edge [omarchy]); an administrator's deliberate combination is kept.
2026-08-30 13:52:06 -04:00
David Heinemeier Hansson 2541eeee3d Merge quattro into hermes-agent
Catches the branch up on 94 commits so what lands here is reviewed against
current quattro, and so #8611 contributes its own five files rather than
dragging a partial catch-up in behind it.
2026-08-30 11:58:37 +02:00
7fec55e0ed Leave Hermes Desktop's HUD the transparency it draws itself
The HUD is a frameless Electron window that paints its own per-pixel
transparency. Under the default rules it gets a compositor border and Omarchy's
window opacity on top, which turns the compact prompt into an outlined, muddy
canvas.

Scoped to the HUD by title, so the main Hermes window keeps the ordinary
treatment.

Co-authored-by: Luiz Filipe <moresco.luiz@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-30 10:30:34 +02:00
David Heinemeier Hansson 56fbaf4689 Merge pull request #8162 from jmwall/fix/windows-vm-opacity
Keep the Windows VM display fully opaque
2026-08-29 17:10:14 +02:00
Ryan Hughes 9da8824098 Merge pull request #8416 from mdisec/theme-name-shell-syntax
Refuse a theme name that is shell syntax, and quote the one the unlock picker returns
2026-08-29 03:19:45 -04:00
David Heinemeier Hansson 6dd9aa55cc Merge pull request #6919 from AksharP5/agent/resolve-dialog-pointer-focus
Stop DaVinci Resolve dialogs from recapturing pointer focus
2026-08-28 23:41:00 +02:00
Ryan Hughes c5a5e14e99 Merge pull request #8072 from basecamp/require-omarchy-repo-signatures
Require signed packages from the Omarchy repository
2026-08-27 11:29:12 -04:00
Omarchybot b68d4142d7 Cut the crash-mute section of the skill to what it instructs
The section had grown a paragraph per review round, each one explaining why the last was right, until one offer took a third of the file. Most of it was reassurance about what the command refuses rather than anything an agent has to do, and the command enforces that itself whatever the prose says.

What is left is the instruction: offer it and never run it unprompted, say how to lift it, which of the two names to pass and why the binary is the better one, quote it because the name is the crashed program's to choose, and name the interpreter collision before muting python or node on someone's behalf. Fifty-four lines to thirty-two, with nothing dropped that changes what the agent does.
2026-08-27 12:28:44 +02:00
David Heinemeier HanssonandClaude Opus 5 f70c55d813 Give Hermes the mark that reads at menu size
U+E90A carried a trace of the Hermes app icon: a portrait whose detail collapses
into a grey smudge beside the ten flat silhouettes the rest of the font is made
of, which is what icon-font.md warns against when it says to pick a source whose
silhouette alone reads.

It is Font Awesome's staff-snake now, under CC BY 4.0 -- the mark Hermes serves
as its favicon and titles its README with. The README records that, along with
the licence the artwork carries, since it is the only note of where these come
from.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 11:45:33 +02:00
OmarchybotandCodex XHigh ea6ee9440a Add omarchy-crash-mute to mute and unmute one program
The mute was reachable only as `omarchy-toggle crash-ignore/<program>`, which asks whoever runs it to know the flag layout, to reduce a binary's path to the name the watcher keys on, and to have read the rule that a name climbing out of that directory writes an unrelated toggle. All of that was carried in the skill's prose, which is the wrong place for a rule that has to hold: prose is advice, and the thing being advised about is a name the crashed program chose.

So it is a command now. `omarchy crash mute hyprland` silences that program, `off` lifts it, `toggle` flips it, and no argument lists what is muted. It takes the binary's path as readily as the name and reduces it the way the watcher does, so the `Executable:` line from `coredumpctl` can be handed straight to it; it refuses what is not one component of a name, so it cannot be talked into writing outside its own directory whatever it is given; and it re-reads the flag afterwards and reports what is now true rather than what was asked for. The listing counts only regular files, because that is all the watcher honours -- anything else in there would read as muted while the crashes kept arriving. A leading `--` is consumed so a program named `-h`, which the router would otherwise answer with its own help, can still be muted.

The watcher gained an unrelated fix that this uncovered. Its fields are read with `IFS=$'\t'`, and tab is IFS whitespace, so an empty field collapsed into the next delimiter and shifted every field after it along one: a crash whose comm was empty had a path read as its pid and was discarded as somebody else's. A process can set its comm to nothing, so that was reachable. Empty fields now arrive as a dash like missing ones, and a dash joins the empty and dot cases that fall back to `unknown`.

Co-Authored-By: Codex XHigh <noreply@openai.com>
2026-08-27 11:26:28 +02:00
OmarchybotandCodex XHigh 8d14869689 Let a crash diagnosis mute that program's notifications
A crash that is understood is not a crash that stops: an upstream bug waiting on a release, a program that dumps core every time it exits. The diagnosis explains it once and the toast keeps arriving, and the only answer Omarchy had was Crash Capture, which turns off every program's notifications in order to silence one.

The watcher already resolves a name to dedupe on and announces that same name in the toast, so the mute is keyed on it: a flag file under toggles/crash-ignore/, written by the existing omarchy-toggle and read by the existing omarchy-toggle-enabled. One flag per name rather than one list, so `on` mutes, `off` un-mutes, and `ls -A` shows what is muted, with no new file format and nothing to parse. It is the executable's basename wherever one was recorded, falling back to the process name, which the kernel truncates to fifteen characters -- muting the truncated form would match nothing, forever, while looking like it worked.

The name is not always a name, though, and the mute turns it into a path. A program picks its own comm and prctl takes anything, including slashes, and the watcher falls back to comm whenever a crash carries no absolute executable. So it is stripped to its last component first: without that, `a/../bar-off` is a legal comm aimed at an unrelated Omarchy flag, letting a crashing program suppress its own notification and letting a user who accepted the offered mute hide their bar instead. Stripping does not always leave a component either -- `/` leaves an empty string, which is no kind of array subscript and no kind of toast, and `.` or `..` names a directory that omarchy-toggle would touch and report success on, leaving a mute that never matches. Both fall back to `unknown`, the word omarchy-agent-crash already uses for a name it does not have, and which mutes like any other.

The skill offers this at the end of a diagnosis and never runs it unprompted, which makes it the single change a diagnosis may make to a system it otherwise only reads. It tells the agent to use the name it was handed rather than re-derive one, since the watcher resolved that name already and the two agree for ordinary names and not for strange ones; a diagnosis started by hand from `omarchy agent crash <pid>` is given no name and gets the derivation instead. It also says to treat the name as hostile text rather than as a word -- it is whatever the crashed program's author called a file, so a single quote inside one closes the quotes around it and the rest runs as the shell -- and to check the flag arrived rather than assume it.

Co-Authored-By: Codex XHigh <noreply@openai.com>
2026-08-27 10:32:10 +02:00
a12a21c02f Add Hermes as a desktop app and a coding agent
Hermes joins Install > AI as a desktop app, sits beside it under Remove > AI,
and becomes a choice in the default-agent list. The CLI installs through
omarchy-install-hermes-cli rather than a bare `mise use`, so its interpreter
is pinned before mise builds it.

Rebased onto quattro. Ori claimed U+E909 in #7709 while this branch was open,
so the Hermes mark moves to U+E90A in the icon font, the menu entries, the
font README, and the charset the menu test pins. The glyph outline itself is
unchanged; it is spliced in beside Ori rather than over it.

Co-Authored-By: witcheer <witcheer.eth@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SySdB3RtCA8BNv6Am246BP
2026-08-26 18:04:41 +02:00
Mehmet InceandClaude Opus 5 75e51f0b95 Refuse a theme name that is shell syntax, and quote the one the unlock picker returns
A theme installed from a git repo is named after the repo URL, and that name
becomes its directory name under ~/.config/omarchy/themes. Style > Unlock built
a command line out of the name the picker returned and handed it to
omarchy-launch-floating-terminal-with-presentation, which runs its argument as a
shell string -- so a theme directory called `a';id;'b` ran `id`. Themes are
already held to contributing colour and nothing that executes, which is why
omarchy-theme-set stages no .lua, terminal config, or vscode.json from one.

Hold the derived name to the characters a theme name needs, which stops it from
being dangerous at every place it lands rather than at the one found, and quote
it with printf %q on the way into the action for the names already on disk.

omarchy-theme-remove keeps its existing path-climb guard: its name reaches only
a quoted rm, and the same charset would strand a theme installed before this.

Reported-by: Luis Alvarez (lalvarezt)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011WFcUm5HWFyxaVYdwAeWPP
2026-08-26 16:22:17 +01:00
9285b19d6a [Security] Stop USB device names from being executed as Hyprland Lua (#8129)
* Stop device names from being executed as Hyprland Lua

Hyprland input-device and monitor names come from USB descriptors and
hyprctl output, so they are attacker-influenceable, yet the toggle and
monitor commands interpolated them straight into hyprctl eval and into
generated Lua that Hyprland re-executes on every reload. The input-device
toggle keys are bound with locked = true, so a malicious USB name reached
Lua code execution from the lock screen; a persisted disable made it run
on every start. This closes that class everywhere it appeared.

- The touchpad/touchscreen disable is now the device name in a plain-text
  sidecar file, read back by a packaged Lua module on reload, never a
  generated Lua file. hyprctl eval Lua-quotes the name and control
  characters are rejected outright.
- Dropped the shipped *-disabled.lua templates so nothing seeds a
  disabled state to /etc/skel, making the name file the single source of
  truth read from a hardcoded ~/.local/state to match the sibling tools.
- The reload loader excludes those two legacy filenames, so a leftover
  generated *-disabled.lua on a not-yet-migrated install can never be
  sourced as code again; a migration then recovers the device name from
  it and deletes it, sanitizing installs that ran the vulnerable version.
- All four monitor scripts (internal, mirror, clamshell, scaling) now
  validate an output name against a plain-connector-name pattern before
  writing it as Lua, closing the same latent pattern in the siblings.
- paths.lua treats a set-but-empty XDG_STATE_HOME as unset, matching the
  bash side so state is never read from the filesystem root.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0144ZDt44vtxjyF8j9Y88NrM

* Let a failing Lua assertion fail the test

lua discards the status of a chunk read from stdin, so a blown assert printed its traceback and still exited 0: the surrounding `set -euo pipefail` never fired and the following `pass` printed `ok`. Every Lua block in these two files was unenforced, including the assertion that a quoted `hyprctl eval` cannot reach `os.execute` and the negative control that proves the test can detect the injection at all. Passing the chunk as a script argument makes lua report the failure.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Re-apply a recovered input-device disable to the running session

The package hook reloads Hyprland during `omarchy-update-system-pkgs`, before `omarchy-migrate` runs, and at that reload the generated Lua is already excluded while the name file does not exist yet — so a touchpad or touchscreen the user had switched off comes back on, and stays on until their next login. Reload once more once the name has been recovered, which is the same path a login already takes to read it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <codex@openai.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Omarchybot <omabot@omarchy.org>
Co-authored-by: Codex XHigh <codex@openai.com>
2026-08-25 11:03:12 +02:00
Jason Wall 8bee78bc63 Keep the Windows VM display fully opaque 2026-08-24 22:11:16 -07:00
Akshar Patel 597f57a198 Allow Resolve Voiceover to release focus 2026-08-24 22:00:42 -04:00
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>
2026-08-24 21:58:27 +02:00
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>
2026-08-24 18:56:00 +02:00
Ryan Hughes e66c27f1e7 Require signed packages from the Omarchy repository 2026-08-24 12:53:24 -04:00
Ryan Hughes 5afc9e1495 Revert "Require signed packages from the Omarchy repository"
Reverts 39cffb8f, which landed on quattro through an accidental direct
push. The change returns for review as a pull request.
2026-08-24 12:53:17 -04:00
Ryan Hughes 39cffb8f51 Require signed packages from the Omarchy repository 2026-08-24 12:37:46 -04:00
Ryan Hughes e3729a385b Send notifications via the Notify D-Bus method, never notify-send
omarchy-notification-send now calls org.freedesktop.Notifications.Notify
directly with `busctl --user`, instead of shelling out to notify-send. Each
value is one typed D-Bus parameter, so there is no argv/option layer that could
reinterpret a relayed headline like `--hint=…` or `-rf` as an option or a hint:
the summary and body are strings, and omarchy-exec-argv is built only from
--exec. A leading `--` keeps busctl's own getopt from reading a dash-leading
value as a busctl option.

Map -i to app_icon, -t to expire_timeout, and urgency to the byte hint; unknown
options are now a hard error rather than a silent pass-through. Route the unused
hypr o.notify helper and the sample hooks through the wrapper too, and tighten
the bin-style test so nothing under bin/ may call notify-send. The test stubs
busctl and trips if notify-send is invoked.
2026-08-23 17:02:29 -04:00
OmarchybotandCodex XHigh 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>
2026-08-23 16:43:31 +02:00
OmarchybotandCodex XHigh 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>
2026-08-23 11:44:23 +02:00
OmarchybotandCodex XHigh 07fccef41c Add Super + Q as a second chord for closing a window (#7767)
* Add Super + Q as a second chord for closing a window

Super + W stays the documented default. Super + Q is the chord people
arrive with from macOS, where Command + Q quits the app, and typing it
into Omarchy did nothing at all until now.

🤖 Generated by Opus 5 in Claude Code. Reviewed by Codex XHigh.

* Put an action's alternative chord on one keybindings row

Super + W and Super + Q both read "Close window" in the menu, two rows
apart, with nothing to say they were the same thing -- and the
alternative sorted above the default. The scratchpad and the calculator
had the same trouble, each bound to a chord and to a second key.

Four actions are named as having an alternative, one at a time, and the
second chord joins the first one's row. A rule would be wrong here: Alt +
Tab and Shift + Alt + Tab both say "Reveal active window on top" while
cycling opposite ways, and a media key is nobody's idea of an alternative
to a Super chord. Both halves still have to agree on what they dispatch,
since a label is only what a chord is called, and an unresolved
dispatcher never counts as agreement.

Nothing is allowed past the 35-character column: a pair that would
overrun it stays as two rows rather than pushing its arrow out of line.
The menu elides a row that outgrows its card -- 754px of label, 78
monospace characters at the heading size -- and the longest entry already
sits at 74, so widening the column to fit the widest pair would have cost
two dozen rows the end of their description.

Priority ordering reads the rendered row, so the chord sharing it would
otherwise reclassify the entry: XF86Calculator alone belongs in the tail
kept for media keys, and it took the calculator down there with it.
Ranking now reads the chord that leads the row.

The key left of 1 reads as ~ rather than Hyprland's name for it, whether
a bind names it or reports the keycode for the keymap to resolve. Cached
records predate all of this, so the cache version moves with it.

🤖 Generated by Opus 5 in Claude Code. Reviewed by Codex XHigh.

Co-authored-by: Codex XHigh <noreply@openai.com>

---------

Co-authored-by: Codex XHigh <noreply@openai.com>
2026-08-22 16:55:01 +02:00
OmarchybotandCodex XHigh 13a969e1ab Only offer Update > Extra Themes when there is one (#7775)
* Only offer Update > Extra Themes when there is one

omarchy-theme-update pulls the themes under ~/.config/omarchy/themes that came from a git clone, so on a machine that has never installed one by hand the row opens a terminal that prints nothing and closes. Guard it with the same predicates the command itself applies, since a row that shows over a symlinked theme or a worktree's `.git` file is the same dead end in a narrower shape, and pin the two to each other in the guard test.

Co-Authored-By: Codex XHigh <noreply@openai.com>

* Extract the Extra Themes guard into omarchy-theme-extras

The row's `when:` and omarchy-theme-update each carried their own idea of which themes came from a git clone, and the two only matched because a test held them together. Name it once instead: omarchy-theme-extras lists those directories and exits nonzero when there are none, so the row asks exactly the command its action runs. Living in a script also puts the glob out of reach of whatever shopt a login shell left set for the guard batch.

Co-Authored-By: Codex XHigh <noreply@openai.com>

---------

Co-authored-by: Codex XHigh <noreply@openai.com>
2026-08-22 16:24:21 +02:00
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>
2026-08-20 22:28:31 +02:00
David Heinemeier Hansson 3451719ca5 Reorder remove to match 2026-08-19 18:03:55 +02:00
David Heinemeier Hansson b3ba358104 Rearrange the order 2026-08-19 17:51:06 +02:00
OmarchybotandCodex XHigh 023021ad2d Add Remove > AI for the apps Install > AI offers (#7504)
* Add Remove > AI for the apps Install > AI offers

Install > AI grew five entries and Remove grew one: Dictation, sitting on its own at the top level. Everything else installed from that menu had to come back out through Remove > Package by name.

Mirror the install tree instead. Dictation moves under the new AI submenu where its installer already lives, and ChatGPT Desktop, Grok Bot, LM Studio, Ollama and T3 Code get removers beside it. Each entry is conditional on the thing actually being installed, so the submenu only ever lists what is there.

What each remover deletes was read off a machine that had all five installed and launched, not guessed. That matters most for T3 Code, which bootstraps the agents it drives: ~/.claude.json, ~/.grok, ~/.npm and ~/.local/share/opencode all appear the first time it runs, and all of them outlive it, so it takes only ~/.config/t3code and ~/.t3. Grok Bot is the same trap in miniature -- ~/.grokbot is its own, ~/.grok belongs to the Grok CLI.

Ollama drops every acceleration variant rather than the one the installer happened to pick, and disables the service before the package, since that is what holds the models open.

🤖 Generated by Opus 5 in Claude Code.

* Remove only what these apps own, and only where removal works

Three defects from an independent review of the previous commit.

ChatGPT Desktop was deleting ~/.cache/codex-runtimes, which belongs to the Codex CLI rather than the desktop app: the `codex` binary resolves its runtime and plugins out of that directory, and it ships in a package this remover does not touch. Removing the desktop app took the CLI's prepared runtime with it, leaving a separate, still-installed tool to rebuild it -- and unable to, offline.

Ollama's row appeared whenever the `ollama` command existed, but omarchy-pkg-drop removes exact package names. With ollama-bin, ollama-git or a hand-built binary the entry offered a removal it could not perform: the service went down, /var/lib/ollama and ~/.ollama were deleted, and it reported success with the program still installed. Every acceleration variant depends on the base package, so testing for that package covers each one the installer can produce and nothing it cannot remove.

LM Studio keeps its models under a relocatable home, and ~/.lmstudio-home-pointer is the only record of where they went. The remover deleted the pointer and the default path, so a user who had moved their models kept every one of them while being told they were gone. Read the pointer before deleting it, and refuse one aimed at / or at the home directory itself, since following it there would take everything.

Co-Authored-By: Codex XHigh <noreply@openai.com>

---------

Co-authored-by: Codex XHigh <noreply@openai.com>
2026-08-19 17:29:36 +02:00