Compare commits
103
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0534987009 | ||
|
|
b9657b6313 | ||
|
|
15b4928e11 | ||
|
|
4ea7ca2b8b | ||
|
|
f4d156c09a | ||
|
|
6ba4ceef57 | ||
|
|
3248aa55c2 | ||
|
|
7cc9057413 | ||
|
|
d57cec42d4 | ||
|
|
c5b59bcfff | ||
|
|
a9e1c01ee9 | ||
|
|
4064436eea | ||
|
|
2cdcafa5b5 | ||
|
|
461cbe9b9c | ||
|
|
256bfeaa75 | ||
|
|
36695fbe84 | ||
|
|
9d95fc7f01 | ||
|
|
e919a64014 | ||
|
|
866d994dcf | ||
|
|
cf459ba752 | ||
|
|
1fe83ff129 | ||
|
|
6a72d4236b | ||
|
|
7a35a8ee9b | ||
|
|
6d327ce83c | ||
|
|
f94c6db34d | ||
|
|
8d1a7ef00c | ||
|
|
6cc561b8d3 | ||
|
|
6cb5396dc7 | ||
|
|
27594ba3f5 | ||
|
|
be6c401200 | ||
|
|
d49dd939b7 | ||
|
|
68e3d7a2ec | ||
|
|
15d64a7025 | ||
|
|
82de446713 | ||
|
|
ebaad12947 | ||
|
|
cf6fedc9f5 | ||
|
|
fb257bf872 | ||
|
|
1cbe72e5dd | ||
|
|
b1cdec9e47 | ||
|
|
af6f64fa3a | ||
|
|
346e69e1ce | ||
|
|
0e1ce609d8 | ||
|
|
3cb3b2d20b | ||
|
|
f8e35ec654 | ||
|
|
f802ae107d | ||
|
|
6f8b62f04c | ||
|
|
9aa0545c2a | ||
|
|
ebd6480387 | ||
|
|
07579f5e94 | ||
|
|
ff4cf8afbc | ||
|
|
cf3d69c38d | ||
|
|
47ce81ebca | ||
|
|
2421e76fb8 | ||
|
|
bffc6a2a5d | ||
|
|
7678e5b0d8 | ||
|
|
be730884f6 | ||
|
|
a73a312e1b | ||
|
|
159163bb16 | ||
|
|
521779b114 | ||
|
|
51af7bf794 | ||
|
|
92c8f87730 | ||
|
|
3bb9867245 | ||
|
|
da0fe6d89f | ||
|
|
2645b82bcd | ||
|
|
9c1ec524cd | ||
|
|
880605f22b | ||
|
|
294c38a2f0 | ||
|
|
1082b7864d | ||
|
|
13f18b2cb7 | ||
|
|
2e989e35e5 | ||
|
|
c6d676f23c | ||
|
|
0f836c0deb | ||
|
|
07adef8a53 | ||
|
|
e713ff3166 | ||
|
|
c7af36d0aa | ||
|
|
b3028f9bd9 | ||
|
|
0f4abeec8e | ||
|
|
7b89780810 | ||
|
|
c0b593b349 | ||
|
|
7fa32bb98c | ||
|
|
2b1d3c4606 | ||
|
|
286b8c2b1c | ||
|
|
f2a2973de8 | ||
|
|
64c5c02f55 | ||
|
|
d0aefc1f79 | ||
|
|
1626405638 | ||
|
|
9b192256e7 | ||
|
|
2487fbcce1 | ||
|
|
9434f220f0 | ||
|
|
9e79b24342 | ||
|
|
6245965cb6 | ||
|
|
f6b4183e4f | ||
|
|
39c2797d3b | ||
|
|
8b979e0c4c | ||
|
|
5c2fb68c0a | ||
|
|
2d3826aff3 | ||
|
|
eb201d0bd3 | ||
|
|
ac1bdbfbf5 | ||
|
|
9f7cdf6983 | ||
|
|
1867dcaa50 | ||
|
|
33d7363c33 | ||
|
|
84336759cc | ||
|
|
5f0704a0f8 |
No files matched your search
@@ -1,2 +0,0 @@
|
||||
# Merges to protected branches need sign-off from an org owner.
|
||||
* @dhh @ryanrhughes
|
||||
@@ -9,24 +9,14 @@ matching guide before starting:
|
||||
- [`agents/skills/icon-font.md`](agents/skills/icon-font.md) - adding branded glyphs to `default/fonts/omarchy/omarchy.ttf`
|
||||
- [`agents/skills/acceptance-tests.md`](agents/skills/acceptance-tests.md) - writing or running graphical acceptance tests under `test/acceptance.d/`
|
||||
- [`agents/skills/visual-verification.md`](agents/skills/visual-verification.md) - verifying any change with a visual effect in the running UI
|
||||
- [`agents/skills/migrations.md`](agents/skills/migrations.md) - creating or changing migrations under `migrations/`
|
||||
|
||||
# Documentation Layout
|
||||
|
||||
Three documentation trees, split by genre and audience:
|
||||
|
||||
- `agents/skills/` - task procedure ("do this when doing X"), for anyone working on the codebase
|
||||
- `docs/` - reference on how the system is shaped (file layout, update pipeline, theming, shell architecture), for anyone working on the codebase; skills link here for depth
|
||||
- `manual/` - end-user documentation for using Omarchy, published; never codebase internals
|
||||
- [`docs/migrations.md`](docs/migrations.md) - creating or changing migrations under `migrations/`
|
||||
|
||||
# Style
|
||||
|
||||
- In markdown documents (`plans/`, `docs/`, `manual/`), write full lines — no hard wrapping at 80 columns; break only at structural boundaries like headings and list items
|
||||
- Two spaces for indentation, no tabs
|
||||
- Use bash 5 conditionals: use `[[ ]]` for string/file tests and `(( ))` for numeric tests
|
||||
- In `[[ ]]`, don't quote variables, but do quote string literals when comparing values (e.g., `[[ $branch == "dev" ]]`)
|
||||
- Prefer `(( ))` over numeric operators inside `[[ ]]` (e.g., `(( count < 50 ))`, not `[[ $count -lt 50 ]]`)
|
||||
- Prefer a full `if`/`else` conditional for simple two-path control flow; don't rely on `exec` or `exit` in one branch to make following statements unreachable
|
||||
- For strings/paths with spaces, quote them instead of escaping spaces with `\ ` (e.g., `"$APP_DIR/Disk Usage.desktop"`, not `$APP_DIR/Disk\ Usage.desktop`)
|
||||
- Shebangs must use `#!/bin/bash` consistently (never `#!/usr/bin/env bash`)
|
||||
- Scripts under `install/` and `migrations/` may be sourced and intentionally omit shebangs
|
||||
@@ -91,8 +81,7 @@ Exceptions are allowed for migration and package-helper scripts where the helper
|
||||
|
||||
# Menu
|
||||
|
||||
- The menu definition lives in `default/omarchy/omarchy-menu.jsonc`;
|
||||
[`docs/menu.md`](docs/menu.md) covers the schema, guards, and providers.
|
||||
- The menu definition lives in `default/omarchy/omarchy-menu.jsonc`.
|
||||
- Do not add `aliases` to new menu entries. Aliases are reserved for
|
||||
established alternate names users already type, kept for compatibility.
|
||||
|
||||
@@ -104,9 +93,7 @@ Exceptions are allowed for migration and package-helper scripts where the helper
|
||||
|
||||
# Tests
|
||||
|
||||
Run focused automated tests for the area you changed;
|
||||
[`docs/testing.md`](docs/testing.md) covers how the suites are shaped. Current
|
||||
test entry points:
|
||||
Run focused automated tests for the area you changed. Current test entry points:
|
||||
|
||||
- `./test/all` - aggregate runner for CLI and shell tests; it intentionally does not run graphical acceptance tests
|
||||
- `./test/cli` - CLI routing, command metadata, theme helpers, and safe dispatch coverage
|
||||
|
||||
@@ -70,10 +70,6 @@ magick -background white -fill black -font default/fonts/omarchy/omarchy.ttf \
|
||||
-pointsize 110 label:@/tmp/row.txt /tmp/font-row.png
|
||||
```
|
||||
|
||||
Then confirm it in the running menu per
|
||||
[`visual-verification.md`](visual-verification.md). Fontconfig prefers the
|
||||
packaged font over a copy in `~/.local/share/fonts` for the same family, so a
|
||||
preview needs either the real file replaced or a `<rejectfont>` rule in
|
||||
`~/.config/fontconfig/conf.d/` pointing fontconfig away from the packaged one.
|
||||
Restart the shell afterwards — Qt reads the font database at startup, so
|
||||
`omarchy menu refresh` alone will not pick up a changed font.
|
||||
Then confirm it in the running menu per [`visual-verification.md`](visual-verification.md). Avoid leaving two fonts with the `omarchy` family registered: Qt can use an old copy in `~/.local/share/fonts` even when `fc-match omarchy` reports the packaged font. For a preview, either replace the packaged file in the disposable VM or temporarily exclude it with a `<rejectfont>` rule in `~/.config/fontconfig/conf.d/` before loading the candidate.
|
||||
|
||||
Refresh the font cache and restart the shell afterwards — Qt reads the font database at startup, so `omarchy menu refresh` alone will not pick up a changed font. Remove temporary fonts and rules after verification.
|
||||
@@ -20,9 +20,7 @@ Run `omarchy-restart-shell` after making changes to QML files.
|
||||
[`docs/omarchy-shell.md`](../../docs/omarchy-shell.md) and
|
||||
`shell/services/PluginRegistry.qml` for the current contract; fields such as
|
||||
`activation` are optional.
|
||||
- Entry-point QML files are `Item`s (not `ShellRoot`), and accept the
|
||||
shell-injected properties `omarchyPath`, `shell`, `manifest`, and
|
||||
`pluginRegistry` / `barWidgetRegistry` as appropriate.
|
||||
- Entry-point QML files are `Item`s (not `ShellRoot`), and accept the shell-injected properties `omarchyPath`, `shell`, `manifest`, and `pluginRegistry` / `barWidgetRegistry` as appropriate. First-party plugins receive the host objects. Third-party plugins receive capability-scoped facades: ordinary plugins may look up and control only their own service and lifecycle, built-in clones retain narrow source-specific configuration and UI compatibility, menu plugins receive an application-library facade, and plugins can read detached scalar bar state; full-bar plugins additionally receive detached bar configuration and widget-catalog snapshots, narrow proxies for the non-authentication services used by built-in bar widgets, and lifecycle control over configured non-authentication UI plugins. Authentication capabilities must be stamped from trusted first-party manifests, and third-party registry views and bar configuration must be detached snapshots rather than shared objects. These facades reduce accidental authority but are not a same-process QML sandbox: a visual bar widget can walk its parent hierarchy to ordinary host objects. Authentication services must therefore remain outside both `ShellRoot._services` and the host QObject tree. Do not expose authentication services through new third-party-facing properties.
|
||||
- Panel / overlay / menu plugins must expose `open(payloadJson)` and
|
||||
`close()` lifecycle methods for `shell summon` and `shell hide`.
|
||||
|
||||
|
||||
+1
-2
@@ -27,7 +27,6 @@ declare -A BINARY_TO_KEY
|
||||
declare -A GROUP_DESCRIPTIONS
|
||||
|
||||
GROUP_DESCRIPTIONS[agent]="AI coding agent usage data"
|
||||
GROUP_DESCRIPTIONS[ascii]="Text drawn as ASCII art"
|
||||
GROUP_DESCRIPTIONS[audio]="Audio input and output controls"
|
||||
GROUP_DESCRIPTIONS[bar]="Omarchy shell bar layout and settings"
|
||||
GROUP_DESCRIPTIONS[battery]="Battery status helpers"
|
||||
@@ -40,7 +39,6 @@ GROUP_DESCRIPTIONS[channel]="Omarchy release channel management"
|
||||
GROUP_DESCRIPTIONS[clipboard]="Clipboard helpers"
|
||||
GROUP_DESCRIPTIONS[cmd]="Command and shortcut helpers"
|
||||
GROUP_DESCRIPTIONS[config]="System configuration helpers"
|
||||
GROUP_DESCRIPTIONS[crash]="Crash notification controls"
|
||||
GROUP_DESCRIPTIONS[debug]="Diagnostics and support logs"
|
||||
GROUP_DESCRIPTIONS[finalize]="Finalize user setup"
|
||||
GROUP_DESCRIPTIONS[default]="Default application selection"
|
||||
@@ -65,6 +63,7 @@ GROUP_DESCRIPTIONS[monitor]="Monitor status helpers"
|
||||
GROUP_DESCRIPTIONS[network]="Network status helpers"
|
||||
GROUP_DESCRIPTIONS[notification]="Notification helpers"
|
||||
GROUP_DESCRIPTIONS[mise]="Mise tool wrappers"
|
||||
GROUP_DESCRIPTIONS[openclaw]="OpenClaw agent platform setup"
|
||||
GROUP_DESCRIPTIONS[osd]="On-screen display status helpers"
|
||||
GROUP_DESCRIPTIONS[pkg]="Package management helpers"
|
||||
GROUP_DESCRIPTIONS[plugin]="Omarchy shell plugin and bar widget management"
|
||||
|
||||
+31
-23
@@ -52,15 +52,14 @@ if omarchy-cmd-missing "$agent"; then
|
||||
fi
|
||||
|
||||
# Agents launched from the keybinding or menu run unattended, so each one starts
|
||||
# with its own spelling of "don't stop to ask". Pi, Ori and Dim have none to
|
||||
# skip.
|
||||
# with its own spelling of "don't stop to ask". Pi has no approval prompts.
|
||||
case "$agent" in
|
||||
opencode)
|
||||
command=(opencode --auto)
|
||||
[[ -n ${prompt:-} ]] && command+=(--prompt "$prompt")
|
||||
;;
|
||||
agy)
|
||||
command=(agy --dangerously-skip-permissions)
|
||||
gemini)
|
||||
command=(gemini --yolo)
|
||||
[[ -n ${prompt:-} ]] && command+=(--prompt-interactive "$prompt")
|
||||
;;
|
||||
copilot)
|
||||
@@ -79,38 +78,47 @@ claude)
|
||||
command=(claude --permission-mode auto)
|
||||
[[ -n ${prompt:-} ]] && command+=(-- "$prompt")
|
||||
;;
|
||||
dim)
|
||||
# `dim exec` is the one-shot prompt form; interactive dim is the bare TUI.
|
||||
if [[ -n ${prompt:-} ]]; then
|
||||
command=(dim exec "$prompt")
|
||||
else
|
||||
command=(dim)
|
||||
fi
|
||||
;;
|
||||
grok)
|
||||
command=(grok --permission-mode bypassPermissions)
|
||||
[[ -n ${prompt:-} ]] && command+=(-- "$prompt")
|
||||
;;
|
||||
kimi)
|
||||
# --prompt is kimi's one-shot form, like `crush run`; --auto spares its questions.
|
||||
command=(kimi --auto)
|
||||
[[ -n ${prompt:-} ]] && command+=(--prompt "$prompt")
|
||||
openclaw)
|
||||
# The launcher owns onboarding and the gateway dance: OpenClaw's terminal UI
|
||||
# must attach to the running gateway (the embedded `openclaw chat` refuses to
|
||||
# start while the gateway owns the state directory). It has no permission
|
||||
# prompts to skip, and --message seeds the session while keeping it
|
||||
# interactive.
|
||||
command=(omarchy-launch-openclaw --tui)
|
||||
[[ -n ${prompt:-} ]] && command+=(--message "$prompt")
|
||||
;;
|
||||
codex)
|
||||
command=(codex --approve-for-me)
|
||||
[[ -n ${prompt:-} ]] && command+=(-- "$prompt")
|
||||
;;
|
||||
cursor-agent)
|
||||
# --yolo covers commands only; the workspace trust dialog has its own flag.
|
||||
# A one-word prompt naming a subcommand (update, login, help) still runs that
|
||||
# subcommand after a bare --, so the agent subcommand is named outright, and
|
||||
# -- after it keeps a prompt starting with a dash from being read as an option.
|
||||
command=(cursor-agent --yolo --trust)
|
||||
[[ -n ${prompt:-} ]] && command+=(agent -- "$prompt")
|
||||
;;
|
||||
hermes)
|
||||
if [[ -n ${prompt:-} ]]; then
|
||||
command=(env -u HERMES_SESSION_SOURCE hermes chat --yolo --tui "--query=$prompt")
|
||||
else
|
||||
command=(hermes --yolo)
|
||||
fi
|
||||
;;
|
||||
muse)
|
||||
# --approval-mode never skips the tool prompts but keeps Muse's own sandbox.
|
||||
command=(muse --approval-mode never)
|
||||
[[ -n ${prompt:-} ]] && command+=(-- "$prompt")
|
||||
;;
|
||||
omp)
|
||||
command=(omp --auto-approve)
|
||||
[[ -n ${prompt:-} ]] && command+=(-- "$prompt")
|
||||
;;
|
||||
ori)
|
||||
# Ori is a harness launcher, and `ori code` is the agent it runs itself.
|
||||
# A prompt alone means one headless turn there, printed after the turn ends,
|
||||
# so --interactive is what seeds the session with it and keeps the window.
|
||||
command=(ori code)
|
||||
[[ -n ${prompt:-} ]] && command+=(--interactive --prompt "$prompt")
|
||||
;;
|
||||
pi)
|
||||
command=(pi)
|
||||
[[ -n ${prompt:-} ]] && command+=("$prompt")
|
||||
|
||||
@@ -16,7 +16,7 @@ flags=()
|
||||
only=()
|
||||
declare -A excluded
|
||||
|
||||
while (( $# > 0 )); do
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--force | --limits-only) flags+=("$1") ;;
|
||||
--except)
|
||||
|
||||
@@ -6,6 +6,12 @@
|
||||
|
||||
set -e
|
||||
|
||||
# Install and upgrade callers can start this helper as root. Ignore their PATH
|
||||
# so optional commands never fall through to a user-writable directory.
|
||||
if (( EUID == 0 )); then
|
||||
export PATH=/usr/share/omarchy/bin:/usr/local/bin:/usr/bin:/bin
|
||||
fi
|
||||
|
||||
target_user=${OMARCHY_INSTALL_USER:-${SUDO_USER:-}}
|
||||
if [[ -z $target_user && -n ${PKEXEC_UID:-} ]]; then
|
||||
target_user=$(getent passwd "$PKEXEC_UID" | cut -d: -f1)
|
||||
@@ -34,7 +40,8 @@ auth required pam_faillock.so authsucc
|
||||
account include system-local-login
|
||||
EOF
|
||||
|
||||
if omarchy-cmd-present fprintd-list && fprintd-list "$target_user" 2>/dev/null | grep -qi finger; then
|
||||
if [[ -x /usr/bin/fprintd-list ]] &&
|
||||
/usr/bin/fprintd-list "$target_user" 2>/dev/null | grep -qi finger; then
|
||||
echo "Configuring lock screen fingerprint authentication..."
|
||||
as_root tee /etc/pam.d/omarchy-lock-fingerprint >/dev/null <<'EOF'
|
||||
#%PAM-1.0
|
||||
|
||||
-1217
File diff suppressed because it is too large.
Load diff
@@ -1,104 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Shared helpers for animating the About branding (source this, don't run it).
|
||||
# omarchy:group=branding
|
||||
# omarchy:name=about-animation
|
||||
# omarchy:hidden=true
|
||||
|
||||
# The About branding's animation: a sheen, and the frames that make it. *When*
|
||||
# those frames are written stays with the caller, because that is inseparable
|
||||
# from how the caller's window closes and resizes; this only says what to write,
|
||||
# and where — it is handed a logo and knows nothing about About beyond that.
|
||||
|
||||
# A band of light leans across the logo, rests, and leans across again. Two
|
||||
# columns per row puts it at 45 degrees on screen, where a cell is twice as tall
|
||||
# as it is wide.
|
||||
ESC=$'\e'
|
||||
# Bright white, so the band does not depend on how the terminal reads bold. Foot's
|
||||
# bold-text-in-bright brightens a bold regular colour into its bright counterpart,
|
||||
# which turns a bold green logo bright green — exactly the colour a bright green
|
||||
# band would have used, leaving a glint nobody can see. No regular colour
|
||||
# brightens into bright white, so this one shows either way.
|
||||
SHEEN_BAND="${ESC}[1m${ESC}[97m"
|
||||
SHEEN_SLANT=2
|
||||
SHEEN_HALF=2
|
||||
SHEEN_FRAME_SECONDS=0.025
|
||||
SHEEN_REST_TICKS=8
|
||||
# Re-measure the grid every half second, the cadence a still logo already cost,
|
||||
# rather than spawning a process per frame.
|
||||
SHEEN_POLL_FRAMES=20
|
||||
|
||||
# A frame is one string holding every row of the logo, positioned and coloured,
|
||||
# so a tick writes the whole logo at once and never shows it half drawn.
|
||||
compose_frame() {
|
||||
local centre=$1
|
||||
local row line length at from to frame=""
|
||||
|
||||
for (( row = 0; row < SHEEN_ROWS; row++ )); do
|
||||
line=${SHEEN_LINES[row]}
|
||||
length=${#line}
|
||||
|
||||
# Where the band crosses this row. Running off the right needs no clamp,
|
||||
# because a slice that starts past the end of a line is already empty, but a
|
||||
# negative offset would count from the end of it instead of off the left.
|
||||
at=$(( centre - row * SHEEN_SLANT ))
|
||||
from=$(( at - SHEEN_HALF ))
|
||||
from=$(( from < 0 ? 0 : from ))
|
||||
to=$(( at + SHEEN_HALF + 1 ))
|
||||
to=$(( to < 0 ? 0 : to ))
|
||||
|
||||
frame+="${ESC}[$((SHEEN_TOP + row));${SHEEN_LEFT}H"
|
||||
frame+="${SHEEN_BASE}${line:0:from}${SHEEN_BAND}${line:from:to - from}${SHEEN_BASE}${line:to}"
|
||||
done
|
||||
|
||||
SHEEN_COMPOSED=$frame
|
||||
}
|
||||
|
||||
# Builds every frame up front: a tick that recomputed a logo's worth of colour
|
||||
# spans in bash would not hold the frame rate, and the sweep is the same every
|
||||
# time. Leaves them in SHEEN_FRAMES, and answers whether this logo can be
|
||||
# animated at all — one it cannot put back exactly as it found it is one to leave
|
||||
# alone, because nothing on screen would say the difference.
|
||||
#
|
||||
# sheen_build <logo-file> <top-row> <left-column> <base-colour> <columns-available>
|
||||
sheen_build() {
|
||||
local file=$1 columns=$5
|
||||
SHEEN_TOP=$2
|
||||
SHEEN_LEFT=$3
|
||||
SHEEN_BASE=$4
|
||||
|
||||
SHEEN_LINES=()
|
||||
# A failing redirection reports itself before 2>/dev/null would apply, so order
|
||||
# it first: the caller's window must not get a shell error painted across it.
|
||||
mapfile -t SHEEN_LINES 2>/dev/null <"$file" || return 1
|
||||
SHEEN_ROWS=${#SHEEN_LINES[@]}
|
||||
(( SHEEN_ROWS > 0 )) || return 1
|
||||
|
||||
local row line width=0
|
||||
for (( row = 0; row < SHEEN_ROWS; row++ )); do
|
||||
line=${SHEEN_LINES[row]}
|
||||
|
||||
# A renderer substitutes $1 to $9 for colours, so a logo written with those is
|
||||
# not the text that reached the screen.
|
||||
[[ $line == *'$'* ]] && return 1
|
||||
|
||||
# These frames slice the row by character and a terminal draws it by column,
|
||||
# so one character has to be one cell — and everything that breaks that breaks
|
||||
# it here. A wide glyph, a combining mark or a joined emoji is not one cell; a
|
||||
# tab or an escape is one the renderer expanded itself; and a shell whose
|
||||
# locale is counting bytes is not counting characters at all.
|
||||
(( ${#line} == $(printf '%s' "$line" | LC_ALL=C.UTF-8 wc -L) )) || return 1
|
||||
|
||||
if (( ${#line} > width )); then
|
||||
width=${#line}
|
||||
fi
|
||||
done
|
||||
(( width > 0 && width <= columns )) || return 1
|
||||
|
||||
SHEEN_FRAMES=()
|
||||
local centre last=$(( width + SHEEN_ROWS * SHEEN_SLANT + SHEEN_HALF ))
|
||||
for (( centre = -SHEEN_HALF; centre <= last; centre++ )); do
|
||||
compose_frame "$centre"
|
||||
SHEEN_FRAMES+=("$SHEEN_COMPOSED")
|
||||
done
|
||||
}
|
||||
@@ -170,7 +170,7 @@ main() {
|
||||
local length payload url
|
||||
|
||||
# Detached worker: this is what actually runs yt-dlp and fires notifications.
|
||||
if [[ ${1:-} == "--download" ]]; then
|
||||
if [[ "${1:-}" == "--download" ]]; then
|
||||
download_url "$2"
|
||||
fi
|
||||
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Silence crash notifications for one program, or list what is silenced
|
||||
# omarchy:args=[--] [<program>] [on|off|toggle]
|
||||
# omarchy:examples=omarchy crash mute | omarchy crash mute hyprland | omarchy crash mute /usr/bin/hyprland | omarchy crash mute hyprland off
|
||||
|
||||
# The flag omarchy-crash-watch reads before announcing a crash. Muting is per
|
||||
# program; Trigger > Toggle > Crash Capture is the switch for all of them.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
readonly MUTES="$HOME/.local/state/omarchy/toggles/crash-ignore"
|
||||
|
||||
usage() {
|
||||
echo "Usage: omarchy crash mute [--] [<program>] [on|off|toggle]" >&2
|
||||
}
|
||||
|
||||
# Only regular files, because that is all the watcher honours: anything else in
|
||||
# there would be reported as muted while the crashes kept arriving. The dotted
|
||||
# glob is for a program legitimately called .hidden, and `.` and `..` fail the
|
||||
# same -f test that keeps them out.
|
||||
list() {
|
||||
local entry found=0
|
||||
|
||||
for entry in "$MUTES"/* "$MUTES"/.*; do
|
||||
[[ -f $entry ]] || continue
|
||||
printf '%s\n' "${entry##*/}"
|
||||
found=1
|
||||
done
|
||||
|
||||
((found)) || echo "No programs muted. Crashes all notify."
|
||||
}
|
||||
|
||||
# A program may be named -h, and the router answers that with its own help
|
||||
# before this ever runs. `omarchy crash mute -- -h` is the way through.
|
||||
[[ ${1:-} == "--" ]] && shift
|
||||
|
||||
if (($# == 0)); then
|
||||
list
|
||||
exit 0
|
||||
fi
|
||||
|
||||
program=$1
|
||||
action=${2:-on}
|
||||
|
||||
# The watcher keys the mute on the executable's basename, so accept the path it
|
||||
# reports as readily as the name, and reduce either the same way it does.
|
||||
program=${program##*/}
|
||||
|
||||
if [[ -z $program || $program == "." || $program == ".." ]]; then
|
||||
echo "Not a program name: $1" >&2
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "$action" in
|
||||
on|off|toggle) ;;
|
||||
*)
|
||||
echo "Not an action: $action" >&2
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
omarchy-toggle "crash-ignore/$program" "$action" || exit 1
|
||||
|
||||
# Report what is now true rather than what was asked for: the flag is what the
|
||||
# watcher reads, and a toggle does not say which way it went.
|
||||
if omarchy-toggle-enabled "crash-ignore/$program"; then
|
||||
echo "Muted crash notifications for $program."
|
||||
else
|
||||
echo "Crash notifications for $program are back on."
|
||||
fi
|
||||
+5
-28
@@ -48,17 +48,12 @@ announce() {
|
||||
# -n 0 so a restart does not re-announce crashes already dealt with.
|
||||
journalctl -f -n 0 -o json "MESSAGE_ID=$COREDUMP_MESSAGE_ID" 2>/dev/null |
|
||||
while IFS= read -r entry; do
|
||||
# A dash for a field that is empty as well as one that is missing: tab is
|
||||
# IFS whitespace, so an empty field collapses into the next delimiter and
|
||||
# every field after it shifts along one. A process can set its own comm to
|
||||
# nothing, and that crash used to be read as somebody else's and dropped.
|
||||
IFS=$'\t' read -r uid comm pid exe signal < <(
|
||||
jq -r 'def field: if . == null or . == "" then "-" else . end;
|
||||
[(._UID | field),
|
||||
(.COREDUMP_COMM | field),
|
||||
(.COREDUMP_PID | field),
|
||||
(.COREDUMP_EXE | field),
|
||||
(.COREDUMP_SIGNAL_NAME | field)] | @tsv' <<<"$entry" 2>/dev/null
|
||||
jq -r '[(._UID // "-"),
|
||||
(.COREDUMP_COMM // "-"),
|
||||
(.COREDUMP_PID // "-"),
|
||||
(.COREDUMP_EXE // "-"),
|
||||
(.COREDUMP_SIGNAL_NAME // "-")] | @tsv' <<<"$entry" 2>/dev/null
|
||||
)
|
||||
|
||||
[[ $pid =~ ^[0-9]+$ ]] || continue
|
||||
@@ -76,29 +71,11 @@ journalctl -f -n 0 -o json "MESSAGE_ID=$COREDUMP_MESSAGE_ID" 2>/dev/null |
|
||||
name=$comm
|
||||
[[ $exe == /* ]] && name=${exe##*/}
|
||||
|
||||
# A process can set its own comm to anything prctl takes, slashes included,
|
||||
# and a crash with no recorded executable falls back to it. The mute below
|
||||
# turns this name into a path, so keep it one component: a crash must not
|
||||
# reach a flag outside crash-ignore/, nor have a diagnosis write one there.
|
||||
name=${name##*/}
|
||||
|
||||
# What that leaves is not always a name. "/" leaves nothing, which is no
|
||||
# kind of array subscript and no kind of toast; a dot component names a
|
||||
# directory rather than a flag, so a mute on it would touch that directory
|
||||
# and then never match; and a dash is what the read above puts there when
|
||||
# the crash recorded no name at all.
|
||||
[[ -n $name && $name != "-" && $name != "." && $name != ".." ]] || name=unknown
|
||||
|
||||
[[ -n $ignore_pattern && $name =~ $ignore_pattern ]] && continue
|
||||
|
||||
# Never announce our own machinery, or it notifies about itself.
|
||||
[[ $name == omarchy-crash-* || $name == omarchy-agent-* ]] && continue
|
||||
|
||||
# Muted at the end of a diagnosis, when the user was offered it and said
|
||||
# yes. A flag per program rather than one list, so omarchy-crash-mute can
|
||||
# lift one without reading, rewriting and re-parsing the rest.
|
||||
omarchy-toggle-enabled "crash-ignore/$name" && continue
|
||||
|
||||
now=$EPOCHSECONDS
|
||||
(((now - ${last_notified[$name]:-0}) < dedupe_seconds)) && continue
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Set and launch the default coding agent
|
||||
# omarchy:args=[pi|omp|opencode|ori|claude|codex|grok|agy|copilot|crush|dim|kimi]
|
||||
# omarchy:args=[pi|omp|opencode|claude|codex|grok|gemini|openclaw|hermes|copilot|crush|cursor-agent|muse]
|
||||
# omarchy:examples=omarchy default agent | omarchy default agent codex | omarchy default agent claude
|
||||
|
||||
installing=false
|
||||
@@ -27,28 +27,56 @@ case "$1" in
|
||||
pi) agent="pi"; name="Pi" ;;
|
||||
omp | oh-my-pi) agent="omp"; name="Oh My Pi"; agent_package="github:can1357/oh-my-pi" ;;
|
||||
opencode | open-code) agent="opencode"; name="OpenCode" ;;
|
||||
ori | openrouter) agent="ori"; name="Ori"; agent_package="github:OpenRouterLabs/ori-releases" ;;
|
||||
claude | claude-code) agent="claude"; name="Claude Code" ;;
|
||||
codex) agent="codex"; name="Codex" ;;
|
||||
crush) agent="crush"; name="Crush" ;;
|
||||
dim | dimcode | dim-agent) agent="dim"; name="Dim Agent"; agent_package="npm:dimcode" ;;
|
||||
grok) agent="grok"; name="Grok"; agent_package="npm:@xai-official/grok" ;;
|
||||
kimi | kimi-code) agent="kimi"; name="Kimi Code"; agent_package="npm:@moonshot-ai/kimi-code" ;;
|
||||
agy | antigravity | antigravity-cli | gemini | gemini-cli) agent="agy"; name="Antigravity"; agent_package="antigravity-cli" ;;
|
||||
gemini | gemini-cli) agent="gemini"; name="Gemini" ;;
|
||||
openclaw) agent="openclaw"; name="OpenClaw"; agent_installer="omarchy-install-openclaw-cli" ;;
|
||||
hermes) agent="hermes"; name="Hermes"; agent_installer="omarchy-install-hermes-cli" ;;
|
||||
copilot | github-copilot) agent="copilot"; name="GitHub Copilot" ;;
|
||||
muse | muse-code | musecode)
|
||||
agent="muse"; name="Muse Code"
|
||||
# Meta's launcher verifies and updates the native binary for this platform.
|
||||
agent_package="http:muse[url=https://api.meta.ai/muse-launcher.sh,bin=muse,version_list_url=https://api.meta.ai/muse-code/channels/muse-stable,version_json_path=.version]"
|
||||
;;
|
||||
cursor | cursor-agent) agent="cursor-agent"; name="Cursor CLI" ;;
|
||||
*)
|
||||
echo "Usage: omarchy-default-agent <pi|omp|opencode|ori|claude|codex|grok|agy|copilot|crush|dim|kimi>"
|
||||
echo "Usage: omarchy-default-agent <pi|omp|opencode|claude|codex|grok|gemini|openclaw|hermes|copilot|crush|cursor-agent|muse>"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
agent_package=${agent_package:-$agent}
|
||||
|
||||
if [[ $installing == "false" ]] && ! mise where "$agent_package" &>/dev/null; then
|
||||
# Hermes reaches mise through its own installer rather than straight from
|
||||
# here: it needs its interpreter pinned, and a bare `mise use` has nowhere to
|
||||
# say so. See omarchy-install-hermes-cli. OpenClaw comes from its pacman
|
||||
# package the same way; see omarchy-install-openclaw-cli.
|
||||
if [[ -n ${agent_installer:-} ]]; then
|
||||
# Not omarchy-cmd-present: the stub is on PATH from first boot and says
|
||||
# nothing about whether Hermes is installed behind it. Treating a cold stub
|
||||
# as installed skips the floating terminal and runs the minute-long install
|
||||
# inside the menu action instead.
|
||||
agent_present() { "$agent_installer" --check; }
|
||||
agent_install() { "$agent_installer" --now; }
|
||||
else
|
||||
# Anything at the wrapper's path other than the wrapper is the user's own
|
||||
# install, such as the symlink Cursor's installer leaves. A mise copy would
|
||||
# only shadow it, since the mise shims precede ~/.local/bin on PATH.
|
||||
user_install() {
|
||||
[[ -x $HOME/.local/bin/$agent ]] &&
|
||||
{ [[ -L $HOME/.local/bin/$agent ]] || ! grep -q '^mise use -g' "$HOME/.local/bin/$agent"; }
|
||||
}
|
||||
agent_present() { user_install || mise where "$agent_package" &>/dev/null; }
|
||||
agent_install() { user_install || mise use -g "$agent_package"; }
|
||||
fi
|
||||
|
||||
if [[ $installing == "false" ]] && ! agent_present; then
|
||||
exec omarchy-launch-floating-terminal-with-presentation omarchy-default-agent --install "$agent"
|
||||
fi
|
||||
|
||||
if ! mise use -g "$agent_package"; then
|
||||
if ! agent_install; then
|
||||
if [[ $installing == "true" ]]; then
|
||||
echo "Could not install $name with mise" >&2
|
||||
else
|
||||
|
||||
@@ -4,12 +4,6 @@
|
||||
# omarchy:args=[chromium|chrome|brave|brave-origin|edge|firefox|zen]
|
||||
# omarchy:examples=omarchy default browser firefox | omarchy default browser brave
|
||||
|
||||
installing=false
|
||||
if [[ ${1:-} == "--install" ]]; then
|
||||
installing=true
|
||||
shift
|
||||
fi
|
||||
|
||||
if (($# == 0)); then
|
||||
case "$(env -u BROWSER xdg-settings get default-web-browser)" in
|
||||
chromium.desktop) echo "chromium" ;;
|
||||
@@ -25,27 +19,19 @@ if (($# == 0)); then
|
||||
fi
|
||||
|
||||
case "$1" in
|
||||
chromium) browser="chromium"; command="chromium"; desktop_id="chromium.desktop"; name="Chromium"; glyph= ;;
|
||||
chrome) browser="chrome"; command="google-chrome-stable"; desktop_id="google-chrome.desktop"; name="Chrome"; glyph= ;;
|
||||
brave) browser="brave"; command="brave"; desktop_id="brave-browser.desktop"; name="Brave"; glyph= ;;
|
||||
brave-origin) browser="brave-origin"; command="brave-origin"; desktop_id="brave-origin.desktop"; name="Brave Origin"; glyph= ;;
|
||||
edge) browser="edge"; command="microsoft-edge-stable"; desktop_id="microsoft-edge.desktop"; name="Edge"; glyph= ;;
|
||||
firefox) browser="firefox"; command="firefox"; desktop_id="firefox.desktop"; name="Firefox"; glyph= ;;
|
||||
zen) browser="zen"; command="zen-browser"; desktop_id="zen.desktop"; name="Zen"; glyph= ;;
|
||||
chromium) desktop_id="chromium.desktop"; name="Chromium"; glyph= ;;
|
||||
chrome) desktop_id="google-chrome.desktop"; name="Chrome"; glyph= ;;
|
||||
brave) desktop_id="brave-browser.desktop"; name="Brave"; glyph= ;;
|
||||
brave-origin) desktop_id="brave-origin.desktop"; name="Brave Origin"; glyph= ;;
|
||||
edge) desktop_id="microsoft-edge.desktop"; name="Edge"; glyph= ;;
|
||||
firefox) desktop_id="firefox.desktop"; name="Firefox"; glyph= ;;
|
||||
zen) desktop_id="zen.desktop"; name="Zen"; glyph= ;;
|
||||
*)
|
||||
echo "Usage: omarchy-default-browser <chromium|chrome|brave|brave-origin|edge|firefox|zen>"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if omarchy-cmd-missing "$command"; then
|
||||
if [[ $installing == "false" ]]; then
|
||||
exec omarchy-launch-floating-terminal-with-presentation omarchy-default-browser --install "$browser"
|
||||
else
|
||||
omarchy-install-browser "$browser" || exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
env -u BROWSER xdg-settings set default-web-browser "$desktop_id" || exit 1
|
||||
|
||||
omarchy-notification-send -g $glyph "$name is now the default browser"
|
||||
@@ -4,12 +4,6 @@
|
||||
# omarchy:args=[code|cursor|zed|sublime_text|helix|vim|emacs|nvim]
|
||||
# omarchy:examples=omarchy default editor | omarchy default editor code | omarchy default editor helix
|
||||
|
||||
installing=false
|
||||
if [[ ${1:-} == "--install" ]]; then
|
||||
installing=true
|
||||
shift
|
||||
fi
|
||||
|
||||
editor_file="$HOME/.local/state/omarchy/defaults/editor"
|
||||
|
||||
if (($# == 0)); then
|
||||
@@ -22,37 +16,20 @@ if (($# == 0)); then
|
||||
fi
|
||||
|
||||
case "$1" in
|
||||
code) selection="code"; editor="code"; name="VSCode"; glyph= ;;
|
||||
cursor) selection="cursor"; editor="cursor"; name="Cursor"; glyph= ;;
|
||||
zed | zeditor) selection="zed"; editor="zeditor"; name="Zed"; glyph= ;;
|
||||
sublime_text) selection="sublime_text"; editor="sublime_text"; name="Sublime Text"; glyph= ;;
|
||||
helix) selection="helix"; editor="helix"; name="Helix"; glyph= ;;
|
||||
vim) selection="vim"; editor="vim"; name="Vim"; glyph= ;;
|
||||
emacs) selection="emacs"; editor="emacs"; name="Emacs"; glyph= ;;
|
||||
nvim) selection="nvim"; editor="nvim"; name="Neovim"; glyph= ;;
|
||||
code) editor="code"; name="VSCode"; glyph= ;;
|
||||
cursor) editor="cursor"; name="Cursor"; glyph= ;;
|
||||
zed | zeditor) editor="zeditor"; name="Zed"; glyph= ;;
|
||||
sublime_text) editor="sublime_text"; name="Sublime Text"; glyph= ;;
|
||||
helix) editor="helix"; name="Helix"; glyph= ;;
|
||||
vim) editor="vim"; name="Vim"; glyph= ;;
|
||||
emacs) editor="emacs"; name="Emacs"; glyph= ;;
|
||||
nvim) editor="nvim"; name="Neovim"; glyph= ;;
|
||||
*)
|
||||
echo "Usage: omarchy-default-editor <code|cursor|zed|sublime_text|helix|vim|emacs|nvim>"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if omarchy-cmd-missing "$editor"; then
|
||||
if [[ $installing == "false" ]]; then
|
||||
exec omarchy-launch-floating-terminal-with-presentation omarchy-default-editor --install "$selection"
|
||||
else
|
||||
case "$selection" in
|
||||
code) omarchy-install-editor-vscode ;;
|
||||
cursor) omarchy-pkg-add cursor-bin ;;
|
||||
zed) omarchy-install-editor-zed ;;
|
||||
sublime_text) omarchy-pkg-add sublime-text-4 ;;
|
||||
helix) omarchy-install-editor-helix ;;
|
||||
vim) omarchy-pkg-add vim ;;
|
||||
emacs) omarchy-install-editor-emacs ;;
|
||||
nvim) omarchy-pkg-add neovim ;;
|
||||
esac || exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "$editor_file")"
|
||||
printf '%s\n' "$editor" >"$editor_file"
|
||||
|
||||
|
||||
@@ -4,12 +4,6 @@
|
||||
# omarchy:args=[alacritty|foot|ghostty|kitty]
|
||||
# omarchy:examples=omarchy default terminal ghostty | omarchy default terminal kitty
|
||||
|
||||
installing=false
|
||||
if [[ ${1:-} == "--install" ]]; then
|
||||
installing=true
|
||||
shift
|
||||
fi
|
||||
|
||||
if (($# == 0)); then
|
||||
desktop_id=$(xdg-terminal-exec --print-id 2>/dev/null || true)
|
||||
desktop_id=${desktop_id%%:*}
|
||||
@@ -24,24 +18,16 @@ if (($# == 0)); then
|
||||
fi
|
||||
|
||||
case "$1" in
|
||||
alacritty) terminal="alacritty"; desktop_id="Alacritty.desktop"; name="Alacritty"; glyph= ;;
|
||||
foot) terminal="foot"; desktop_id="foot.desktop"; name="Foot"; glyph= ;;
|
||||
ghostty) terminal="ghostty"; desktop_id="com.mitchellh.ghostty.desktop"; name="Ghostty"; glyph= ;;
|
||||
kitty) terminal="kitty"; desktop_id="kitty.desktop"; name="Kitty"; glyph= ;;
|
||||
alacritty) desktop_id="Alacritty.desktop"; name="Alacritty"; glyph= ;;
|
||||
foot) desktop_id="foot.desktop"; name="Foot"; glyph= ;;
|
||||
ghostty) desktop_id="com.mitchellh.ghostty.desktop"; name="Ghostty"; glyph= ;;
|
||||
kitty) desktop_id="kitty.desktop"; name="Kitty"; glyph= ;;
|
||||
*)
|
||||
echo "Usage: omarchy-default-terminal <alacritty|foot|ghostty|kitty>"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if omarchy-cmd-missing "$terminal"; then
|
||||
if [[ $installing == "false" ]]; then
|
||||
exec omarchy-launch-floating-terminal-with-presentation omarchy-default-terminal --install "$terminal"
|
||||
else
|
||||
omarchy-install-terminal "$terminal" || exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
cat >~/.config/xdg-terminals.list <<EOF
|
||||
# Terminal emulator preference order for xdg-terminal-exec
|
||||
# The first found and valid terminal will be used
|
||||
|
||||
@@ -129,7 +129,8 @@ term_pt_for() {
|
||||
'BEGIN { printf "%d", int(s * p / b + 0.5) }'
|
||||
}
|
||||
|
||||
# Set the font point size in every terminal config that exists. Family is left
|
||||
# Set the font point size in terminal configs, creating Kitty overrides when
|
||||
# it inherits its size from the system config. Family is left
|
||||
# untouched — that is omarchy-font-set's job. Live-reload signals mirror
|
||||
# omarchy-font-set; foot has no reload signal, so running instances are nudged.
|
||||
set_terminal_size() {
|
||||
@@ -139,8 +140,13 @@ set_terminal_size() {
|
||||
sed -i -E "s/^size[[:space:]]*=.*/size = $pt/" ~/.config/alacritty/alacritty.toml
|
||||
fi
|
||||
|
||||
if [[ -f ~/.config/kitty/kitty.conf ]]; then
|
||||
sed -i -E "s/^font_size[[:space:]]+.*/font_size $pt.0/" ~/.config/kitty/kitty.conf
|
||||
if [[ -f ~/.config/kitty/kitty.conf ]] || omarchy-cmd-present kitty; then
|
||||
mkdir -p ~/.config/kitty
|
||||
if grep -qE '^[[:space:]]*font_size[[:space:]]+' ~/.config/kitty/kitty.conf 2>/dev/null; then
|
||||
sed --follow-symlinks -i -E "s/^[[:space:]]*font_size[[:space:]]+.*/font_size $pt.0/" ~/.config/kitty/kitty.conf
|
||||
else
|
||||
printf '\nfont_size %s.0\n' "$pt" >>~/.config/kitty/kitty.conf
|
||||
fi
|
||||
pkill -USR1 kitty 2>/dev/null || true
|
||||
fi
|
||||
|
||||
@@ -177,9 +183,13 @@ term_current_pt() {
|
||||
elif [[ -f ~/.config/alacritty/alacritty.toml ]]; then
|
||||
grep -oP '^size[[:space:]]*=[[:space:]]*\K[0-9.]+' ~/.config/alacritty/alacritty.toml | head -1
|
||||
elif [[ -f ~/.config/kitty/kitty.conf ]]; then
|
||||
grep -oP '^font_size[[:space:]]+\K[0-9.]+' ~/.config/kitty/kitty.conf | head -1
|
||||
local pt
|
||||
pt=$(grep -oP '^[[:space:]]*font_size[[:space:]]+\K[0-9.]+' ~/.config/kitty/kitty.conf | tail -1)
|
||||
echo "${pt:-$TERM_DEFAULT_PT}"
|
||||
elif [[ -f ~/.config/foot/foot.ini ]]; then
|
||||
grep -oP ':size=\K[0-9.]+' ~/.config/foot/foot.ini | head -1
|
||||
elif omarchy-cmd-present kitty; then
|
||||
echo "$TERM_DEFAULT_PT"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
@@ -30,8 +30,14 @@ if [[ -f ~/.config/alacritty/alacritty.toml ]]; then
|
||||
sed -i "s/family = \".*\"/family = \"$font_name\"/g" ~/.config/alacritty/alacritty.toml
|
||||
fi
|
||||
|
||||
if [[ -f ~/.config/kitty/kitty.conf ]]; then
|
||||
sed -i "s/^font_family .*/font_family $font_name/g" ~/.config/kitty/kitty.conf
|
||||
if [[ -f ~/.config/kitty/kitty.conf ]] || omarchy-cmd-present kitty; then
|
||||
mkdir -p ~/.config/kitty
|
||||
if grep -qE '^[[:space:]]*font_family[[:space:]]+' ~/.config/kitty/kitty.conf 2>/dev/null; then
|
||||
kitty_font_name=$(printf '%s' "$font_name" | sed 's/[\\&/]/\\&/g')
|
||||
sed --follow-symlinks -i -E "s/^[[:space:]]*font_family[[:space:]]+.*/font_family $kitty_font_name/" ~/.config/kitty/kitty.conf
|
||||
else
|
||||
printf '\nfont_family %s\n' "$font_name" >>~/.config/kitty/kitty.conf
|
||||
fi
|
||||
pkill -USR1 kitty
|
||||
fi
|
||||
|
||||
|
||||
@@ -30,6 +30,35 @@ MKINITCPIO_CONF="/etc/mkinitcpio.conf.d/omarchy_resume.conf"
|
||||
SWAP_FILE="/swap/swapfile"
|
||||
RESUME_DROP_IN="/etc/limine-entry-tool.d/resume.conf"
|
||||
|
||||
install_root_file() {
|
||||
local source="$1"
|
||||
local destination="$2"
|
||||
local mode="$3"
|
||||
local stage
|
||||
|
||||
stage=$(sudo /usr/bin/mktemp -- "${destination%/*}/.${destination##*/}.omarchy.XXXXXX") || return 1
|
||||
safe_stage_path "$stage" "$destination" || return 1
|
||||
|
||||
if sudo /usr/bin/install -m "$mode" -o root -g root -T "$source" "$stage" &&
|
||||
sudo /usr/bin/mv -Tf -- "$stage" "$destination"; then
|
||||
return 0
|
||||
else
|
||||
safe_stage_path "$stage" "$destination" && sudo /usr/bin/rm -f -- "$stage"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
safe_stage_path() {
|
||||
local stage="$1"
|
||||
local destination="$2"
|
||||
local prefix suffix
|
||||
|
||||
prefix="${destination%/*}/.${destination##*/}.omarchy."
|
||||
[[ $stage == "$prefix"* ]] || return 1
|
||||
suffix=${stage#"$prefix"}
|
||||
[[ $suffix =~ ^[[:alnum:]]{6}$ ]]
|
||||
}
|
||||
|
||||
# Check if hibernation is already configured
|
||||
if [[ -f $MKINITCPIO_CONF ]] && grep -q "^HOOKS+=(resume)$" "$MKINITCPIO_CONF"; then
|
||||
# Fix empty resume_offset if btrfs map-swapfile failed during initial setup
|
||||
@@ -83,14 +112,20 @@ if ! swapon --show | grep -q "$SWAP_FILE"; then
|
||||
sudo swapon -p 0 "$SWAP_FILE"
|
||||
fi
|
||||
|
||||
# Ensure keyboard backlight doesn't prevent sleep
|
||||
# Install this before writing the resume marker so a failed install remains
|
||||
# retryable through the normal setup command.
|
||||
if ! install_root_file "$OMARCHY_PATH/default/systemd/system-sleep/keyboard-backlight" \
|
||||
/usr/lib/systemd/system-sleep/keyboard-backlight 0755; then
|
||||
echo "Could not install the keyboard-backlight system-sleep hook" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Add resume hook to mkinitcpio
|
||||
sudo mkdir -p /etc/mkinitcpio.conf.d
|
||||
echo "Adding resume hook to $MKINITCPIO_CONF"
|
||||
echo "HOOKS+=(resume)" | sudo tee "$MKINITCPIO_CONF" >/dev/null
|
||||
|
||||
# Ensure keyboard backlight doesn't prevent sleep
|
||||
sudo cp -p "$OMARCHY_PATH/default/systemd/system-sleep/keyboard-backlight" /usr/lib/systemd/system-sleep/
|
||||
|
||||
# Add resume= kernel parameters so the initramfs resume hook knows where to find the
|
||||
# hibernation image. Without these, resume happens late (after GPU drivers load) and fails.
|
||||
if [[ ! -f $RESUME_DROP_IN ]]; then
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Match the Dell XPS 13 DX13260 that requires the sidecar amplifier workaround.
|
||||
|
||||
product_sku="${OMARCHY_DMI_PRODUCT_SKU:-/sys/class/dmi/id/product_sku}"
|
||||
|
||||
omarchy-hw-match "DX13260" &&
|
||||
grep -qix "0E53" "$product_sku" 2>/dev/null
|
||||
Executable
+159
@@ -0,0 +1,159 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Install the Hermes desktop app
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
set -e
|
||||
|
||||
if (( EUID == 0 )); then
|
||||
echo "Run this command as your desktop user, without sudo." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Installing Hermes Desktop..."
|
||||
omarchy-pkg-add hermes-desktop
|
||||
|
||||
if [[ ! -r /usr/share/hermes-desktop/install.sh || ! -r /usr/share/hermes-desktop/runtime.patch ]] ||
|
||||
! release_commit=$(jq -er 'select(.branch == "main") | .commit | select(test("^[0-9a-f]{40}$"))' /opt/hermes-desktop/resources/install-stamp.json 2>/dev/null); then
|
||||
echo "The installed Hermes package cannot prepare in-app updates. Run 'omarchy update', then try again." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# If Hermes was already installed for the terminal, the app supersedes it: one
|
||||
# machine, one Hermes. This drops that copy so the terminal, the default agent
|
||||
# and the app all end up on the app's installation.
|
||||
omarchy-install-hermes-cli || true
|
||||
|
||||
# Keep the runtime at the root even when invoked from a Hermes profile.
|
||||
HERMES_HOME=$(realpath -ms -- "${HERMES_HOME:-$HOME/.hermes}")
|
||||
home_parent=$(dirname -- "$HERMES_HOME")
|
||||
if [[ ${home_parent##*/} == [Pp][Rr][Oo][Ff][Ii][Ll][Ee][Ss] ]]; then
|
||||
HERMES_HOME=$(dirname -- "$home_parent")
|
||||
fi
|
||||
export HERMES_HOME
|
||||
|
||||
runtime="$HERMES_HOME/hermes-agent"
|
||||
native_app="$runtime/apps/desktop/release/linux-unpacked"
|
||||
|
||||
runtime_ready() {
|
||||
[[ -f $runtime/.hermes-bootstrap-complete && -f $runtime/venv/bin/hermes && -x $runtime/venv/bin/hermes && -f $runtime/venv/bin/python && -x $runtime/venv/bin/python ]] &&
|
||||
timeout 15 "$runtime/venv/bin/hermes" --version >/dev/null 2>&1
|
||||
}
|
||||
|
||||
check_main() {
|
||||
local main_commit
|
||||
main_commit=$(git -C "$runtime" rev-parse --verify refs/heads/main 2>/dev/null || true)
|
||||
if [[ -n $main_commit && $main_commit != "$release_commit" && $main_commit != "$(git -C "$runtime" rev-parse --verify refs/remotes/origin/main 2>/dev/null)" ]]; then
|
||||
echo "Hermes main has local commits. Keep that work and prepare the desktop with 'hermes desktop --build-only'." >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
if ! runtime_ready; then
|
||||
# The upstream installer can reset an existing checkout. Do not pin a newer
|
||||
# or modified runtime back to the package release while repairing setup.
|
||||
if [[ -e $runtime || -L $runtime ]]; then
|
||||
if [[ $(git -C "$runtime" rev-parse HEAD 2>/dev/null) != "$release_commit" ]] ||
|
||||
[[ -n $(git -C "$runtime" status --porcelain --untracked-files=all) ]]; then
|
||||
echo "Hermes setup is incomplete at $runtime. Repair that installation before trying again; existing files have been kept." >&2
|
||||
exit 1
|
||||
fi
|
||||
check_main
|
||||
fi
|
||||
|
||||
# Upstream replaces these commands, including foreign files and symlinks.
|
||||
# Keep their original bytes/links before handing the names to the desktop.
|
||||
command_backup=""
|
||||
for command in hermes hermes-agent hermes-acp; do
|
||||
command_path="$HOME/.local/bin/$command"
|
||||
if [[ -e $command_path || -L $command_path ]]; then
|
||||
if [[ ! -f $command_path && ! -L $command_path ]]; then
|
||||
echo "Cannot replace $command_path: move it aside before installing Hermes Desktop." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z $command_backup ]]; then
|
||||
command_backup=$(mktemp -d "$HOME/.local/bin/.hermes-before-desktop.XXXXXX")
|
||||
echo "Saving existing Hermes commands in $command_backup"
|
||||
fi
|
||||
cp -a -- "$command_path" "$command_backup/"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Setting up the Hermes runtime..."
|
||||
bash /usr/share/hermes-desktop/install.sh --skip-setup --branch main --commit "$release_commit" --force-commit --dir "$runtime" --hermes-home "$HERMES_HOME"
|
||||
if ! runtime_ready; then
|
||||
echo "Hermes runtime setup did not complete. Re-run this command after resolving the installer error." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
runtime_commit=$(git -C "$runtime" rev-parse HEAD)
|
||||
if [[ $runtime_commit == "$release_commit" ]]; then
|
||||
# The updater switches to main before checking for changes. Start main at
|
||||
# the packaged release, with enough history for its first fast-forward.
|
||||
check_main
|
||||
if [[ $(git -C "$runtime" rev-parse --is-shallow-repository) == "true" ]]; then
|
||||
git -C "$runtime" fetch --unshallow origin main
|
||||
fi
|
||||
git -C "$runtime" switch -C main "$release_commit"
|
||||
|
||||
if git -C "$runtime" apply --check /usr/share/hermes-desktop/runtime.patch >/dev/null 2>&1; then
|
||||
git -C "$runtime" apply /usr/share/hermes-desktop/runtime.patch
|
||||
elif ! git -C "$runtime" apply --reverse --check /usr/share/hermes-desktop/runtime.patch >/dev/null 2>&1; then
|
||||
echo "The Hermes Linux runtime patch conflicts with local changes. Existing files have been kept." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -e $native_app || -L $native_app ]]; then
|
||||
if [[ ! -f $native_app/Hermes || ! -x $native_app/Hermes || ! -f $native_app/resources/app.asar || ! -f $native_app/resources/install-stamp.json ]]; then
|
||||
echo "The Hermes desktop app at $native_app is incomplete. Repair it with 'hermes desktop --build-only' before trying again." >&2
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
if [[ $runtime_commit != "$release_commit" ]]; then
|
||||
echo "The Hermes runtime has moved beyond the packaged desktop release. Run 'hermes desktop --build-only', then try again." >&2
|
||||
exit 1
|
||||
fi
|
||||
desktop_changes=$(git -C "$runtime" status --porcelain --untracked-files=all -- apps/desktop package.json package-lock.json)
|
||||
if [[ -n $desktop_changes ]]; then
|
||||
echo "Hermes desktop sources have local changes. Run 'hermes desktop --build-only', then try again; existing files have been kept." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p -- "${native_app%/*}"
|
||||
staging=$(mktemp -d "${native_app%/*}/.linux-unpacked.XXXXXX")
|
||||
trap 'rm -rf -- "$staging"' EXIT
|
||||
cp -a /opt/hermes-desktop/. "$staging/"
|
||||
chmod 0755 "$staging/chrome-sandbox"
|
||||
mv -T --no-clobber -- "$staging" "$native_app"
|
||||
if [[ -e $staging ]]; then
|
||||
echo "A Hermes desktop app appeared during setup. It has been kept; please try again." >&2
|
||||
exit 1
|
||||
fi
|
||||
trap - EXIT
|
||||
|
||||
# Record this matching prebuilt app using the CLI's own content hash, so
|
||||
# subsequent menu launches do not rebuild an app that is already current.
|
||||
env -u PYTHONPATH -u PYTHONHOME "$runtime/venv/bin/python" - "$runtime" <<'PY'
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, sys.argv[1])
|
||||
from hermes_cli.main import _write_desktop_build_stamp
|
||||
|
||||
_write_desktop_build_stamp(Path(sys.argv[1]), source_mode=False)
|
||||
PY
|
||||
fi
|
||||
|
||||
echo "Opening Hermes Desktop..."
|
||||
setsid uwsm-app -- /usr/bin/hermes-desktop >/dev/null 2>&1 &
|
||||
|
||||
# Only a running Hermes can be told which skin to show; a unit outlives this
|
||||
# terminal and reports to the journal.
|
||||
echo "Matching Hermes to the current theme once it is set up..."
|
||||
systemctl --user stop omarchy-hermes-theme.service 2>/dev/null || true
|
||||
systemd-run --user --quiet --collect --unit=omarchy-hermes-theme omarchy-theme-set-hermes --wait
|
||||
|
||||
echo ""
|
||||
echo "Hermes Desktop has been installed."
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Install the OpenClaw agent platform and its Control UI web app
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
set -e
|
||||
|
||||
echo "Installing OpenClaw..."
|
||||
omarchy-pkg-add openclaw
|
||||
|
||||
# The desktop app is OpenClaw's Control UI: a web app served by its own
|
||||
# gateway. The launcher entry goes through omarchy-launch-openclaw, which
|
||||
# onboards or starts that gateway before opening the window. The icon ships
|
||||
# inside the package, so nothing is fetched here.
|
||||
echo "Installing the OpenClaw web app..."
|
||||
omarchy-webapp-install OpenClaw "http://127.0.0.1:18789" \
|
||||
/usr/lib/node_modules/openclaw/dist/control-ui/apple-touch-icon.png \
|
||||
omarchy-launch-openclaw
|
||||
|
||||
# A first install runs onboarding right here: launching the app instead would
|
||||
# open a second floating terminal for the wizard, identical to this one.
|
||||
# omarchy-openclaw-onboard runs the wizard the way this flow needs (terminal
|
||||
# prompts, gateway as a user service, and it actually returns). A machine
|
||||
# that is already onboarded goes straight to the app.
|
||||
if [[ -f $HOME/.openclaw/openclaw.json ]]; then
|
||||
echo "Opening OpenClaw..."
|
||||
setsid uwsm-app -- gtk-launch OpenClaw >/dev/null 2>&1 &
|
||||
elif omarchy-openclaw-onboard && [[ -f $HOME/.openclaw/openclaw.json ]]; then
|
||||
echo "Opening OpenClaw..."
|
||||
setsid uwsm-app -- gtk-launch OpenClaw >/dev/null 2>&1 &
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "OpenClaw has been installed."
|
||||
echo "If you skipped onboarding, launching OpenClaw from the app grid resumes it."
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Install T3 Code and point it at the Omarchy palette
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
set -e
|
||||
|
||||
T3CODE_HOME="${T3CODE_HOME:-$HOME/.t3}"
|
||||
T3CODE_STATE_DIR="$T3CODE_HOME/userdata"
|
||||
|
||||
echo "Installing T3 Code..."
|
||||
omarchy-pkg-add t3code-bin
|
||||
|
||||
echo "Matching T3 Code to the current theme..."
|
||||
mkdir -p "$T3CODE_STATE_DIR"
|
||||
|
||||
# An updated user's current theme may have been staged before this template existed.
|
||||
if [[ ! -f $HOME/.local/state/omarchy/current/theme/t3code.json ]]; then
|
||||
omarchy-theme-refresh
|
||||
fi
|
||||
|
||||
omarchy-theme-set-t3code
|
||||
|
||||
# The packaged CLI selects the published palette before the app's first launch.
|
||||
t3 theme set omarchy --base-dir "$T3CODE_HOME"
|
||||
|
||||
echo "Opening T3 Code..."
|
||||
setsid uwsm-app -- gtk-launch t3code >/dev/null 2>&1 &
|
||||
|
||||
echo ""
|
||||
echo "T3 Code has been installed."
|
||||
@@ -1,10 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Install a supported browser
|
||||
# omarchy:args=<chromium|chrome|brave|brave-origin|edge|firefox|zen>
|
||||
# omarchy:examples=omarchy install browser chromium | omarchy install browser firefox
|
||||
|
||||
set -e
|
||||
# omarchy:args=<chrome|brave|brave-origin|edge|firefox|zen>
|
||||
# omarchy:examples=omarchy install browser firefox | omarchy install browser brave
|
||||
|
||||
source "$OMARCHY_PATH/install/helpers/browser-policy.sh"
|
||||
|
||||
@@ -30,15 +28,6 @@ setup_firefox_wayland() {
|
||||
}
|
||||
|
||||
case $1 in
|
||||
chromium)
|
||||
echo "Installing Chromium..."
|
||||
omarchy-pkg-add chromium
|
||||
|
||||
setup_chromium_policy_directory /etc/chromium/policies/managed
|
||||
copy_chromium_flags ~/.config/chromium-flags.conf
|
||||
omarchy-theme-set-browser
|
||||
announce_browser_installed "Chromium"
|
||||
;;
|
||||
chrome)
|
||||
echo "Installing Chrome..."
|
||||
omarchy-pkg-aur-add google-chrome || exit 1
|
||||
@@ -92,7 +81,7 @@ zen)
|
||||
announce_browser_installed "Zen"
|
||||
;;
|
||||
*)
|
||||
echo "Usage: omarchy-install-browser <chromium|chrome|brave|brave-origin|edge|firefox|zen>"
|
||||
echo "Usage: omarchy-install-browser <chrome|brave|brave-origin|edge|firefox|zen>"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
Executable
+280
@@ -0,0 +1,280 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Install the Hermes CLI as a mise-backed wrapper in ~/.local/bin
|
||||
# omarchy:args=[--check|--now|--owns|--remove]
|
||||
# omarchy:examples=omarchy install hermes cli | omarchy install hermes cli --now
|
||||
|
||||
# Hermes pins every one of its dependencies exactly and declares
|
||||
# Requires-Python >=3.11,<3.14, so it can neither be built against Arch's
|
||||
# Python nor share the python-* packages. mise builds it a private environment
|
||||
# instead.
|
||||
#
|
||||
# It gets its own installer rather than a line in omarchy-mise-install because
|
||||
# of the interpreter pin. Given no compatible interpreter to hand, uv builds
|
||||
# the venv against the system Python in violation of Hermes' own bound,
|
||||
# reports success, and leaves the breakage to surface later inside a
|
||||
# dependency -- and omarchy-mise-install writes a fixed stub with nowhere to
|
||||
# say otherwise.
|
||||
#
|
||||
# There is only ever one Hermes on a machine. hermes-desktop cannot run against
|
||||
# this one -- it needs a runtime built from its own commit, and the version gap
|
||||
# fails its readiness probe -- so it installs its own under ~/.hermes and puts
|
||||
# that on PATH. When the package is present it therefore owns Hermes outright:
|
||||
# this installer stands aside and removes its own copy, so the terminal, the
|
||||
# default agent and the app are all the same installation.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
mode=${1:-}
|
||||
|
||||
tool='pipx:hermes-agent[extras=all]'
|
||||
python='3.13'
|
||||
|
||||
# The line that identifies the stub as this installer's; matched whole, so a
|
||||
# wrapper that merely mentions the command is not mistaken for ours.
|
||||
marker='# Written by omarchy-install-hermes-cli.'
|
||||
|
||||
# The package, not the runtime directory: it is installed before the app has
|
||||
# ever run, and that is exactly when we must not start building a second copy.
|
||||
desktop_owns_hermes() {
|
||||
omarchy-pkg-present hermes-desktop
|
||||
}
|
||||
|
||||
# The venv appears at the python-deps stage, several stages before the one that
|
||||
# installs the command, so its presence says nothing about being usable. The
|
||||
# marker is written last, and the command is what the agent actually runs.
|
||||
desktop_hermes_ready() {
|
||||
[[ -f $HOME/.hermes/hermes-agent/.hermes-bootstrap-complete ]] || return 1
|
||||
|
||||
# An executable of that name proves nothing about whose it is; the app's own
|
||||
# points into ~/.hermes, and anything else is not the install we are asking
|
||||
# about. Matched as a plain string, because the path carries a dot and an
|
||||
# unanchored pattern would also claim a wrapper pointing at ~/xhermes.
|
||||
[[ -f $HOME/.local/bin/hermes ]] || return 1
|
||||
grep -qF "$HOME/.hermes" "$HOME/.local/bin/hermes" || return 1
|
||||
|
||||
# And a marker left behind by an install whose venv has since gone answers
|
||||
# for nothing, so the command has to run, exactly as a foreign one must.
|
||||
hermes_prompt_ready
|
||||
}
|
||||
|
||||
# Whether Hermes is really installed, not merely whether the stub exists. A
|
||||
# stub on its own is cold: running it installs Hermes, which takes minutes.
|
||||
installed() {
|
||||
[[ -d "$(mise where "$tool" 2>/dev/null)/hermes-agent/lib/python$python" ]]
|
||||
}
|
||||
|
||||
# The stub is the only thing this installer owns. Anything else at that path
|
||||
# -- Hermes' official installer, a hand-rolled wrapper, even a dangling link
|
||||
# -- was put there by the user and is never deleted or overwritten here.
|
||||
# Symlinks count as foreign even when they resolve to a marked file: the stub
|
||||
# is written as a regular file, so a link is someone else's arrangement.
|
||||
ours() {
|
||||
[[ -f $HOME/.local/bin/hermes && ! -L $HOME/.local/bin/hermes ]] &&
|
||||
grep -qxF "$marker" "$HOME/.local/bin/hermes"
|
||||
}
|
||||
|
||||
foreign_hermes() {
|
||||
[[ -e $HOME/.local/bin/hermes || -L $HOME/.local/bin/hermes ]] && ! ours
|
||||
}
|
||||
|
||||
# A hermes at that path is usable when it is a command that runs: a regular
|
||||
# executable whose --version answers. The executable bit alone proves little -- a directory
|
||||
# passes -x on search permission, and a wrapper whose interpreter or target is
|
||||
# gone passes it too. The desktop app applies the same probe with the same 15
|
||||
# second budget, so what passes here is what it will use.
|
||||
hermes_runs() {
|
||||
[[ -f $HOME/.local/bin/hermes && -x $HOME/.local/bin/hermes ]] &&
|
||||
timeout 15 "$HOME/.local/bin/hermes" --version >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# A flag counts only when the help defines it, not whenever it is mentioned:
|
||||
# what follows must be a shape argparse prints after a definition -- the usage
|
||||
# line's closing bracket, the gap before same-line help text, an uppercase
|
||||
# metavar, or the end of the line. Prose like "With --tui: run ..." stays
|
||||
# prose, and --tui-theme or --tui_mode never answers for --tui. Not probed by
|
||||
# parsing an actual invocation on purpose: a release that ignores unknown
|
||||
# arguments would turn the probe into a live session.
|
||||
help_defines_flag() {
|
||||
grep -qE -- "$1(]|[[:space:]][[:upper:]]|[[:space:]]{2}|$)" <<<"$2"
|
||||
}
|
||||
|
||||
# Probed for the flags omarchy-agent actually passes -- --query to seed the
|
||||
# session and --tui to keep it interactive -- rather than a marker standing in
|
||||
# for them: the old probe keyed on chat carrying --oneshot, which no released
|
||||
# Hermes did (it lived at the top level until v0.21 added chat's own), so
|
||||
# every release read as "not ready".
|
||||
hermes_prompt_ready() {
|
||||
local help
|
||||
hermes_runs &&
|
||||
help=$(timeout 15 "$HOME/.local/bin/hermes" chat --help 2>/dev/null) &&
|
||||
help_defines_flag '--tui' "$help" &&
|
||||
help_defines_flag '--query' "$help"
|
||||
}
|
||||
|
||||
# --owns answers whether the wrapper on PATH is the one this command wrote, so
|
||||
# the migration and Remove Preinstalls do not each carry their own copy of the
|
||||
# marker and drift from it.
|
||||
if [[ $mode == "--owns" ]]; then
|
||||
if ours; then exit 0; else exit 1; fi
|
||||
fi
|
||||
|
||||
# --remove tears down a Hermes CLI this installer put in place -- the mise tool
|
||||
# it installed and the stub it marked -- so Remove Hermes clears a CLI the app
|
||||
# never superseded (an interrupted install, or the terminal CLI from before the
|
||||
# app existed) rather than leaving it stranded on PATH. The whole teardown
|
||||
# turns on the marked stub, exactly as replacement does further down: without
|
||||
# it nothing proves the mise environment is Omarchy's rather than one the user
|
||||
# built against the same spec, and a user's stays theirs. The desktop takeover
|
||||
# removes the environment without asking, but that is its own bargain -- a
|
||||
# second Hermes has to go whoever built it, and the app still provides the
|
||||
# command afterwards; here nothing would. Idempotent: nothing owned, nothing
|
||||
# to do.
|
||||
if [[ $mode == "--remove" ]]; then
|
||||
if ours; then
|
||||
mise rm -g "$tool" >/dev/null 2>&1 || true
|
||||
mise uninstall --all "$tool" >/dev/null 2>&1 || true
|
||||
rm -f "$HOME/.local/bin/hermes" 2>/dev/null || true
|
||||
|
||||
# Every step is attempted before any is judged, and judged by what is left
|
||||
# rather than by what the commands claimed: the marked stub still answering
|
||||
# hermes, or mise still resolving the tool, is a CLI still installed no
|
||||
# matter how the removal exited.
|
||||
# Not "run --remove again": once the stub is gone nothing marks the mise
|
||||
# environment as ours, so a rerun would find nothing it owns and succeed
|
||||
# without touching what was left. Only the full commands finish the job.
|
||||
if ours || mise where "$tool" >/dev/null 2>&1; then
|
||||
echo "Could not remove the Hermes CLI Omarchy installed. Finish by hand:" >&2
|
||||
echo " rm -f ~/.local/bin/hermes" >&2
|
||||
echo " mise rm -g '$tool'" >&2
|
||||
echo " mise uninstall --all '$tool'" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# --check lets callers tell a cold stub from a working one before they commit
|
||||
# to a path that assumes Hermes is ready.
|
||||
if [[ $mode == "--check" ]]; then
|
||||
if desktop_owns_hermes; then
|
||||
if desktop_hermes_ready; then exit 0; else exit 1; fi
|
||||
fi
|
||||
# A foreign command is ready only when it also supports prompted sessions;
|
||||
# since it is not ours to replace, nothing this installer does will update it.
|
||||
if foreign_hermes; then
|
||||
if hermes_prompt_ready; then exit 0; else exit 1; fi
|
||||
fi
|
||||
if installed && hermes_prompt_ready; then exit 0; else exit 1; fi
|
||||
fi
|
||||
|
||||
# Hand Hermes over to the app rather than keeping a second copy beside it.
|
||||
if desktop_owns_hermes; then
|
||||
# Not gated on that copy being healthy: `mise up` can rebuild it against the
|
||||
# wrong interpreter and a half-finished install answers to neither test, and
|
||||
# either way it is still a second Hermes. Removing nothing is harmless.
|
||||
if mise where "$tool" >/dev/null 2>&1; then
|
||||
echo "Hermes Desktop provides Hermes; removing the separate CLI install..." >&2
|
||||
fi
|
||||
|
||||
mise rm -g "$tool" >/dev/null 2>&1 || true
|
||||
mise uninstall --all "$tool" >/dev/null 2>&1 || true
|
||||
|
||||
# Our own stub has to go with it. Left in place it still answers `hermes`
|
||||
# until the app's bootstrap overwrites it, and answering means building the
|
||||
# second Hermes this whole arrangement exists to avoid.
|
||||
if ours; then
|
||||
rm -f "$HOME/.local/bin/hermes"
|
||||
fi
|
||||
|
||||
if desktop_hermes_ready; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Hermes Desktop is installed but has not set Hermes up yet." >&2
|
||||
echo "Launch Hermes Desktop once to finish installing it." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The user already has a hermes of their own. Leave it be: a working one is
|
||||
# what the default agent will run, and a broken one is theirs to fix.
|
||||
if foreign_hermes; then
|
||||
if hermes_prompt_ready; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if hermes_runs; then
|
||||
echo "~/.local/bin/hermes does not support the interactive seeded sessions Omarchy needs." >&2
|
||||
echo "Update it to a Hermes Agent release with interactive chat queries, then run omarchy-install-hermes-cli again." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "~/.local/bin/hermes exists but is not runnable, and it was not installed by Omarchy." >&2
|
||||
echo "Fix or remove it, then run omarchy-install-hermes-cli again." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Only the marked wrapper proves the matching mise environment is ours to replace.
|
||||
if installed && ! hermes_prompt_ready; then
|
||||
if ours; then
|
||||
echo "Updating Hermes for prompted sessions..." >&2
|
||||
mise rm -g "$tool" >/dev/null 2>&1 || true
|
||||
mise uninstall --all "$tool" >/dev/null 2>&1 || true
|
||||
else
|
||||
echo "A Hermes mise environment exists without an Omarchy-owned wrapper." >&2
|
||||
echo "Update or remove it explicitly, then run omarchy-install-hermes-cli again." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
mkdir -p "$HOME/.local/bin"
|
||||
rm -f "$HOME/.local/bin/hermes"
|
||||
|
||||
cat >"$HOME/.local/bin/hermes" <<EOF
|
||||
#!/bin/bash
|
||||
|
||||
$marker
|
||||
|
||||
export UV_PYTHON="$python"
|
||||
|
||||
# Exported rather than set on the install line alone, so the version resolved
|
||||
# to run agrees with the one just installed. Hermes ships several times a week
|
||||
# and mise's cooldown would otherwise hold a new release back for days.
|
||||
export MISE_MINIMUM_RELEASE_AGE=0
|
||||
|
||||
# mise up -- which omarchy update runs -- reinstalls without that pin, so this
|
||||
# asks which interpreter is actually there rather than whether anything is.
|
||||
if ! [[ -d "\$(mise where '$tool' 2>/dev/null)/hermes-agent/lib/python$python" ]]; then
|
||||
echo "Installing Hermes on Python $python (this takes a minute)..." >&2
|
||||
|
||||
# mise's pipx backend shells out to uv, which a stock Omarchy does not have.
|
||||
# It is fetched here rather than when this stub was written, so setting up a
|
||||
# machine that never runs Hermes costs nothing.
|
||||
if omarchy-cmd-missing uv && ! mise where uv >/dev/null 2>&1; then
|
||||
mise use -g --quiet uv@latest || exit 1
|
||||
fi
|
||||
|
||||
mise use -g --quiet --force '$tool' || exit 1
|
||||
fi
|
||||
|
||||
# The pin belongs to building Hermes, not to everything Hermes then runs.
|
||||
# Exported it would reach the agent and every command it shells out to, so a
|
||||
# uv in the user's own project would resolve 3.13 there too -- uv only warns
|
||||
# when that contradicts the project's requires-python, and builds it anyway.
|
||||
exec env -u UV_PYTHON mise x '$tool' -- hermes "\$@"
|
||||
EOF
|
||||
|
||||
chmod +x "$HOME/.local/bin/hermes"
|
||||
|
||||
# The desktop app resolves a hermes on PATH by running `hermes --version` with
|
||||
# a 15 second budget, then falls back to cloning its own copy when that times
|
||||
# out. A first-run mise install does not fit in 15 seconds, so anything that
|
||||
# hands Hermes to the GUI has to install it here rather than leave it stubbed.
|
||||
if [[ $mode == "--now" ]]; then
|
||||
"$HOME/.local/bin/hermes" --version
|
||||
if ! hermes_prompt_ready; then
|
||||
echo "Hermes installed without the interactive seeded sessions Omarchy needs." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Ensure the OpenClaw CLI is installed for the default agent
|
||||
# omarchy:args=[--check|--now]
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
# OpenClaw is not a mise tool: the openclaw pacman package is the one OpenClaw
|
||||
# installation on the machine — the CLI, the gateway, and the Install > AI web
|
||||
# app all share it, and the fast ring keeps it current. The default-agent flow
|
||||
# talks to that package through the same --check/--now contract mise-backed
|
||||
# agents get from mise itself.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
case "${1:---now}" in
|
||||
--check)
|
||||
omarchy-pkg-present openclaw
|
||||
;;
|
||||
--now)
|
||||
if ! omarchy-pkg-present openclaw; then
|
||||
echo "Installing OpenClaw..."
|
||||
omarchy-pkg-add openclaw
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Usage: omarchy-install-openclaw-cli [--check|--now]" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -10,4 +10,4 @@ echo "Enabling ONCE background service..."
|
||||
sudo systemctl enable --now once-background.service
|
||||
|
||||
echo -e "\nLaunching ONCE..."
|
||||
sudo once
|
||||
once
|
||||
@@ -4,8 +4,11 @@
|
||||
|
||||
set -e
|
||||
|
||||
# 1Password reads the display scale itself, the way Electron apps do, and comes
|
||||
# up oversized next to every other window on a scaled monitor. The packaged
|
||||
# .desktop pins it too; this covers the hotkey, which runs the binary directly.
|
||||
if omarchy-cmd-present 1password; then
|
||||
exec setsid uwsm-app -- 1password
|
||||
exec setsid uwsm-app -- 1password --force-device-scale-factor=1
|
||||
else
|
||||
exec omarchy-launch-floating-terminal-with-presentation omarchy-install-service-1password
|
||||
fi
|
||||
+13
-196
@@ -10,50 +10,10 @@
|
||||
|
||||
LOGO_FILE="$HOME/.config/omarchy/branding/about.txt"
|
||||
FIT_FILE="$HOME/.local/state/omarchy/windows/about.fit"
|
||||
OMARCHY_FASTFETCH_DIR=/etc/fastfetch
|
||||
|
||||
# The logo block in the fastfetch config. The fit below reproduces this layout to
|
||||
# size the window, and the sheen has to repaint the very cells fastfetch drew the
|
||||
# logo on, so both read the padding from here.
|
||||
LOGO_PAD_LEFT=2
|
||||
LOGO_PAD_TOP=2
|
||||
LOGO_PAD_RIGHT=6
|
||||
|
||||
# The content is measured once and then goes on living: an uptime that turns
|
||||
# minutes into hours and hours into days, a version string that grows, a module
|
||||
# that shows up on the next boot. A window fitted to exactly what was measured
|
||||
# has nowhere to put any of it, and the layout clips or scrolls the moment it
|
||||
# grows — which is also the moment the logo stops being where it was drawn. Keep
|
||||
# a little in hand rather than measure again every time something ticks over.
|
||||
FIT_SPARE_COLUMNS=2
|
||||
FIT_SPARE_ROWS=1
|
||||
|
||||
POLL_SECONDS=0.5
|
||||
|
||||
# fastfetch has no animation of its own, so the sheen is ours. It knows about a
|
||||
# logo and nothing about About, which is why it is a file of its own.
|
||||
source omarchy-branding-about-animation
|
||||
|
||||
# A user's own fastfetch config can relocate or restyle the logo in ways this
|
||||
# measurement cannot see, so leave sizing to the float rule in that case. It can
|
||||
# sit in any of several directories fastfetch searches ahead of Omarchy's own, so
|
||||
# ask fastfetch for that order rather than keep a copy here for its next release
|
||||
# to outdate.
|
||||
# A user-level fastfetch config can relocate or restyle the logo in ways this
|
||||
# measurement cannot see, so leave sizing to the float rule in that case.
|
||||
custom_fastfetch_config() {
|
||||
local directory listed=false
|
||||
|
||||
# A whole line at a time, because a home directory may contain a space, and the
|
||||
# marker fastfetch puts beside the config it settled on is not part of the path.
|
||||
while IFS= read -r directory; do
|
||||
listed=true
|
||||
directory=${directory% (\*)}
|
||||
[[ ${directory%/} == "$OMARCHY_FASTFETCH_DIR" ]] && return 1
|
||||
[[ -f ${directory%/}/config.jsonc ]] && return 0
|
||||
done < <(fastfetch --list-config-paths 2>/dev/null)
|
||||
|
||||
# Silence is not the same answer as "none of them", so fall back to the
|
||||
# directory fastfetch has always looked in first rather than read it as one.
|
||||
[[ $listed == true ]] && return 1
|
||||
[[ -f $HOME/.config/fastfetch/config.jsonc ]]
|
||||
}
|
||||
|
||||
@@ -140,22 +100,17 @@ fit_window() {
|
||||
|
||||
# The guard character keeps command substitution from eating the trailing
|
||||
# break line, which provides the bottom padding row.
|
||||
local modules module_w
|
||||
local modules module_w module_h
|
||||
modules=$(fastfetch --logo none | sed 's/\x1b\[[0-9;?]*[a-zA-Z]//g'; printf X)
|
||||
modules=${modules%X}
|
||||
module_w=$(printf '%s' "$modules" | display_columns)
|
||||
|
||||
# Ask fastfetch how tall its layout came out rather than predicting it from the
|
||||
# logo and the module column: once the logo is the taller of the two, fastfetch
|
||||
# writes a row more than that arithmetic expects, and a window sized by it
|
||||
# scrolls the top padding away.
|
||||
measure_layout || return 1
|
||||
module_h=$(printf '%s' "$modules" | wc -l)
|
||||
|
||||
# Mirror the logo block in the fastfetch config: 2 columns of padding left of
|
||||
# the logo, 6 between logo and modules. Then 2 columns of right padding to
|
||||
# match, a row for the cursor so the trailing break shows, and the spare above.
|
||||
local target_c=$(( LOGO_PAD_LEFT + logo_w + LOGO_PAD_RIGHT + module_w + LOGO_PAD_LEFT + FIT_SPARE_COLUMNS ))
|
||||
local target_r=$(( LAYOUT_ROWS + 1 + FIT_SPARE_ROWS ))
|
||||
# the logo, 6 between logo and modules, 2 rows above it. Then 2 columns of
|
||||
# right padding to match, and a row for the cursor so the trailing break shows.
|
||||
local target_c=$(( 2 + logo_w + 6 + module_w + 2 ))
|
||||
local target_r=$(( (logo_h + 2 > module_h ? logo_h + 2 : module_h) + 1 ))
|
||||
|
||||
local nudges=0 rows cols address width height shift_w shift_h target_w target_h
|
||||
while :; do
|
||||
@@ -191,155 +146,17 @@ fit_window() {
|
||||
return 1
|
||||
}
|
||||
|
||||
# One run answers both questions the sheen has to ask first. How tall the layout
|
||||
# is, because a window too small for it scrolls, which moves the logo off the rows
|
||||
# the frames address. And what colour fastfetch drew the logo in, because the
|
||||
# glint has to hand every cell back in the colour it arrived in — assume it, and
|
||||
# a logo fastfetch colours differently comes out of the first glint a new one.
|
||||
measure_layout() {
|
||||
[[ -n ${LAYOUT_ROWS:-} ]] && return 0
|
||||
|
||||
local rendered plain needle offset found row
|
||||
|
||||
# --pipe false because fastfetch drops its colours when it is not writing to a
|
||||
# terminal, and it is writing to this substitution.
|
||||
rendered=$(fastfetch --pipe false 2>/dev/null; printf X)
|
||||
rendered=${rendered%X}
|
||||
LAYOUT_ROWS=$(printf '%s' "$rendered" | wc -l)
|
||||
(( LAYOUT_ROWS > 0 )) || return 1
|
||||
|
||||
# Find the logo in what fastfetch drew rather than working it out from the
|
||||
# padding this file was written against. The config that runs is the one in
|
||||
# /etc, which a checkout does not replace, so the two can disagree — and a logo
|
||||
# measured two rows above where it was drawn is a logo the sheen moves. Not
|
||||
# finding it at all is the same answer as finding it somewhere unexpected:
|
||||
# whatever is on screen is not the text in the file, so leave it alone.
|
||||
IFS=$'\t' read -r offset needle < <(logo_landmark) || return 1
|
||||
[[ -n $needle ]] || return 1
|
||||
|
||||
plain=$(printf '%s' "$rendered" | sed 's/\x1b\[[0-9;?]*[a-zA-Z]//g')
|
||||
found=$(printf '%s' "$plain" | LC_ALL=C.UTF-8 awk -v needle="$needle" \
|
||||
'index($0, needle) { print NR, index($0, needle); exit }')
|
||||
[[ -n $found ]] || return 1
|
||||
read -r LOGO_ROW LOGO_COLUMN <<<"$found"
|
||||
LOGO_ROW=$(( LOGO_ROW - offset ))
|
||||
(( LOGO_ROW >= 1 && LOGO_COLUMN >= 1 )) || return 1
|
||||
|
||||
# Whatever fastfetch set before that row is what the sheen has to give back.
|
||||
row=$(printf '%s' "$rendered" | sed -n "$(( LOGO_ROW + offset ))p")
|
||||
LOGO_COLOR=""
|
||||
[[ $row =~ ^(($ESC\[[0-9;]*m)+) ]] && LOGO_COLOR=${BASH_REMATCH[1]}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# The longest line of the logo, and how far down the logo it sits — the most
|
||||
# distinctive thing to look for in the render, and the offset that turns where it
|
||||
# was found back into where the logo starts.
|
||||
# Tab-separated, and the offset first, because the line may hold spaces of its
|
||||
# own and splitting on them would cut the landmark short.
|
||||
logo_landmark() {
|
||||
LC_ALL=C.UTF-8 awk '
|
||||
{ if (length($0) > best) { best = length($0); line = $0; at = NR - 1 } }
|
||||
END { if (best > 0) printf "%d\t%s\n", at, line }
|
||||
' "$LOGO_FILE" 2>/dev/null
|
||||
}
|
||||
|
||||
# The About screen's own reasons the logo might not be where these frames would
|
||||
# draw it. Whether the logo itself can be animated is the sheen's own question.
|
||||
build_sheen() {
|
||||
# Whatever the last build left is not this window's, and the loop below plays
|
||||
# whatever is here — so a build that fails has to leave nothing to play.
|
||||
SHEEN_FRAMES=()
|
||||
|
||||
custom_fastfetch_config && return 1
|
||||
|
||||
# fastfetch honours NO_COLOR when it writes to a terminal but not when it writes
|
||||
# to the measurement below, so a logo drawn without colour would be measured as
|
||||
# green and left green by the first glint. A glint is colour anyway, which is
|
||||
# the thing NO_COLOR asks for none of.
|
||||
[[ -n ${NO_COLOR:-} ]] && return 1
|
||||
|
||||
measure_layout || return 1
|
||||
|
||||
# The layout needs a row for the cursor past its last line. Without one it has
|
||||
# scrolled, and the logo is no longer on the rows the frames address. Ask the
|
||||
# terminal where the cursor actually is rather than trust the arithmetic, and
|
||||
# keep the arithmetic for a terminal that will not say.
|
||||
local rows cols
|
||||
read -r rows cols <<<"$(stty size)"
|
||||
(( rows > LAYOUT_ROWS )) || return 1
|
||||
|
||||
# The cell the logo's first row starts on, every attribute fastfetch left on
|
||||
# those cells so a glint that has passed leaves them as it found them, and the
|
||||
# room it has to work in left of the module column.
|
||||
sheen_build "$LOGO_FILE" "$LOGO_ROW" "$LOGO_COLUMN" "${ESC}[0m${LOGO_COLOR}" "$(( cols - LOGO_COLUMN + 1 ))"
|
||||
}
|
||||
|
||||
# What the frames were built against. A window that resized, or a logo that was
|
||||
# rebranded, needs fastfetch run again before anything is drawn over it.
|
||||
content_changed() {
|
||||
[[ $resized == true ]] && return 0
|
||||
|
||||
[[ $(stty size) != "$grid" || $(stat -c %Y "$LOGO_FILE" 2>/dev/null) != "$logo_stamp" ]]
|
||||
}
|
||||
|
||||
# A tick either times out, which is the delay, or a key arrives and About closes.
|
||||
# Anything else on stdin is a terminal that went away, which closes it too.
|
||||
tick() {
|
||||
read -t "$1" -n 1 -s && exit
|
||||
(( $? > 128 )) || exit
|
||||
}
|
||||
|
||||
play_sheen() {
|
||||
local index
|
||||
|
||||
for (( index = 0; index < ${#SHEEN_FRAMES[@]}; index++ )); do
|
||||
# Stop before painting a frame rather than after: a resize has already moved
|
||||
# the cells these address, and the rest of a sweep would land across them.
|
||||
# The grid costs a process, so it stays on the poll interval. The trap costs
|
||||
# nothing, so it is read last — a signal that arrived while the grid was being
|
||||
# read would otherwise be seen only after another frame had gone out.
|
||||
(( index % SHEEN_POLL_FRAMES == 0 )) && content_changed && return 1
|
||||
[[ $resized == true ]] && return 1
|
||||
|
||||
printf '%s' "${SHEEN_FRAMES[index]}"
|
||||
tick "$SHEEN_FRAME_SECONDS"
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# The logo is still between glints, so About is a quiet window to leave open.
|
||||
rest_sheen() {
|
||||
local ticks
|
||||
|
||||
for (( ticks = 0; ticks < SHEEN_REST_TICKS; ticks++ )); do
|
||||
tick "$POLL_SECONDS"
|
||||
content_changed && return 1
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
if [[ ${1:-} == "--render" ]]; then
|
||||
printf '\e[?25l'
|
||||
|
||||
# A sweep runs for seconds between polls, so it reads this instead. The polling
|
||||
# stays as the backstop, for a signal that arrived while it could not be taken.
|
||||
resized=false
|
||||
trap 'resized=true' WINCH
|
||||
|
||||
# Give the compositor a moment to apply the window rules before measuring cells.
|
||||
settle_grid
|
||||
|
||||
fitted=false
|
||||
passes=0
|
||||
while :; do
|
||||
grid=$(stty size)
|
||||
size=$(stty size)
|
||||
logo_stamp=$(stat -c %Y "$LOGO_FILE" 2>/dev/null)
|
||||
resized=false
|
||||
LAYOUT_ROWS=""
|
||||
clear
|
||||
fastfetch
|
||||
# A second pass picks up a fit that could not measure the window the first
|
||||
@@ -348,10 +165,10 @@ if [[ ${1:-} == "--render" ]]; then
|
||||
if [[ $fitted == false ]] && (( ++passes <= 2 )); then
|
||||
fit_window && fitted=true
|
||||
fi
|
||||
# An empty frame list plays nothing, so a logo that cannot be animated waits
|
||||
# here exactly as the still one did, and there is one loop rather than two.
|
||||
build_sheen
|
||||
while play_sheen && rest_sheen; do :; done
|
||||
while [[ $(stty size) == $size && $(stat -c %Y "$LOGO_FILE" 2>/dev/null) == $logo_stamp ]]; do
|
||||
read -t 0.5 -n 1 -s && exit
|
||||
(( $? > 128 )) || exit
|
||||
done
|
||||
# A rebranded logo changes the content dimensions, so measure again.
|
||||
if [[ $(stat -c %Y "$LOGO_FILE" 2>/dev/null) != $logo_stamp ]]; then
|
||||
fitted=false
|
||||
|
||||
Executable
+91
@@ -0,0 +1,91 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Open the OpenClaw Control UI (or its terminal UI with --tui), onboarding or starting the gateway first when needed.
|
||||
# omarchy:args=[--tui [--message <text>]]
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
tui=false
|
||||
message=()
|
||||
if [[ ${1:-} == "--tui" ]]; then
|
||||
tui=true
|
||||
shift
|
||||
if [[ ${1:-} == "--message" ]]; then
|
||||
message=(--message "${2:?--message needs a value}")
|
||||
shift 2
|
||||
fi
|
||||
fi
|
||||
|
||||
# Onboarding is OpenClaw's own interactive wizard, run through
|
||||
# omarchy-openclaw-onboard so it stays in the terminal, installs the gateway
|
||||
# as a user service, and returns (see that script for why bare `openclaw
|
||||
# onboard` does none of those as of 2026.9.1). The config check gates what
|
||||
# follows because the wizard's "Skip for now" also exits 0: only inference
|
||||
# that passed writes the config, and skipping must not loop back into the
|
||||
# wizard.
|
||||
if [[ ! -f $HOME/.openclaw/openclaw.json ]]; then
|
||||
if [[ $tui == "true" ]]; then
|
||||
# Already in a terminal, so the wizard runs right here.
|
||||
omarchy-openclaw-onboard
|
||||
[[ -f $HOME/.openclaw/openclaw.json ]] || exit 1
|
||||
else
|
||||
# The wizard needs a real terminal, and chaining the relaunch means
|
||||
# finishing it lands the user in the app. Single quotes so $HOME expands
|
||||
# in the spawned terminal.
|
||||
# shellcheck disable=SC2016
|
||||
exec omarchy-launch-floating-terminal-with-presentation \
|
||||
'omarchy-openclaw-onboard && [[ -f $HOME/.openclaw/openclaw.json ]] && omarchy-launch-openclaw'
|
||||
fi
|
||||
fi
|
||||
|
||||
dashboard_url() {
|
||||
# browserUrl carries a single-use browser handoff; url is the shared-auth
|
||||
# fallback for gateways predating the handoff flow. The timeout keeps a
|
||||
# wedged CLI from hanging an app-grid launch that has no terminal to ^C.
|
||||
timeout 10 openclaw dashboard --json 2>/dev/null | jq -re '.browserUrl // .url // empty'
|
||||
}
|
||||
|
||||
# --json never starts the gateway, so an empty answer means it is not running.
|
||||
# Recovery goes through the gateway's own service commands. `dashboard --yes`
|
||||
# used to be the start/install-without-prompting path, but as of 2026.9.1 it
|
||||
# defers to "the owning supervisor" for both a missing and a stopped unit,
|
||||
# and once the gateway is up it copies a one-time pairing URL into the
|
||||
# clipboard, which nothing here needs. Enablement, not the unit file, decides:
|
||||
# a unit that was written but never enabled (an install that died halfway)
|
||||
# would otherwise be "started" once and stay off at every following login,
|
||||
# where --force rewrites and enables it.
|
||||
if ! url=$(dashboard_url); then
|
||||
if systemctl --user is-enabled --quiet openclaw-gateway.service 2>/dev/null; then
|
||||
timeout 60 openclaw gateway start >&2 || true
|
||||
else
|
||||
timeout 120 openclaw gateway install --force >&2 || true
|
||||
fi
|
||||
|
||||
# A first-ever service install (unit write, daemon-reload, first boot)
|
||||
# takes materially longer than starting an installed unit, so the budget
|
||||
# is sized for the slow case.
|
||||
for _ in {1..30}; do
|
||||
url=$(dashboard_url) && break
|
||||
sleep 1
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ -z ${url:-} ]]; then
|
||||
echo "OpenClaw's gateway did not come up. Check it with: openclaw gateway status" >&2
|
||||
echo "If onboarding never finished, rerun it with: omarchy-openclaw-onboard" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ $tui == "true" ]]; then
|
||||
# Attach to the gateway rather than `openclaw chat`: chat is the embedded
|
||||
# local runtime, which refuses to start while the gateway owns ~/.openclaw's
|
||||
# state directory -- and on any machine set up through Install > AI, the
|
||||
# gateway service is running whenever the desktop session is.
|
||||
exec openclaw tui "${message[@]}"
|
||||
fi
|
||||
|
||||
# The handoff URL rides in the browser's argv, which uwsm's app daemon echoes
|
||||
# into the user journal. Accepted: the token is single-use with a ten-minute
|
||||
# expiry against a loopback-only gateway, and journal access already implies
|
||||
# access to ~/.openclaw itself.
|
||||
exec omarchy-launch-webapp "$url"
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
# omarchy:summary=Launch the Omarchy screensaver in the default terminal on the system with the correct font configuration.
|
||||
|
||||
if omarchy-cmd-missing ttfx; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Exit early if screensaver is already running
|
||||
pgrep -f '[o]rg.omarchy.screensaver' && exit 0
|
||||
|
||||
|
||||
@@ -38,12 +38,6 @@ parse_keycodes() {
|
||||
if (code != "" && symbol != "" && symbol != "NoSymbol") keycode_symbol[code] = toupper(symbol)
|
||||
}
|
||||
|
||||
# Read off the keysym rather than the keycode, so the key left of 1 reads
|
||||
# as the symbol printed on it whichever key the active layout puts it on.
|
||||
for (code in keycode_symbol) {
|
||||
if (keycode_symbol[code] == "GRAVE") keycode_symbol[code] = "~"
|
||||
}
|
||||
|
||||
mouse_symbol["272"] = "LEFT MOUSE BUTTON"
|
||||
mouse_symbol["273"] = "RIGHT MOUSE BUTTON"
|
||||
mouse_symbol["274"] = "MIDDLE MOUSE BUTTON"
|
||||
@@ -311,7 +305,6 @@ dynamic_bindings() {
|
||||
|
||||
case "$key" in
|
||||
comma) key="COMMA" ;;
|
||||
grave) key="~" ;;
|
||||
period) key="PERIOD" ;;
|
||||
minus) key="MINUS" ;;
|
||||
equal) key="EQUAL" ;;
|
||||
@@ -331,41 +324,15 @@ static_bindings() {
|
||||
echo "SHIFT ALT,D,Download Video from Web App,sendshortcut,SHIFT ALT,D,"
|
||||
}
|
||||
|
||||
# Actions Omarchy binds to a second chord meant as an alternative, named one at
|
||||
# a time. 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. Only chords a user reaches
|
||||
# for interchangeably belong on one row.
|
||||
alternative_chord_actions() {
|
||||
cat <<'ACTIONS'
|
||||
Close window
|
||||
Calculator
|
||||
Toggle scratchpad
|
||||
Move window to scratchpad
|
||||
ACTIONS
|
||||
}
|
||||
|
||||
# Parse and format keybindings
|
||||
#
|
||||
# `awk` does the heavy lifting:
|
||||
# - Set the field separator to a comma ','.
|
||||
# - Joins the key combination (e.g., "SUPER + Q").
|
||||
# - Joins the command that the key executes.
|
||||
# - Puts an action's alternative chord on the row with the first one.
|
||||
# - Prints display text and dispatch metadata as tab-separated fields.
|
||||
parse_binding_records() {
|
||||
awk -F, -v alternatives="$(alternative_chord_actions)" '
|
||||
BEGIN {
|
||||
# The column every row pads its chords to. Nothing is allowed past it: the
|
||||
# menu renders in monospace, and a row that overruns pushes its arrow out
|
||||
# of a column the eye reads straight down.
|
||||
column = 35;
|
||||
|
||||
split(alternatives, named, "\n");
|
||||
for (i in named) {
|
||||
if (named[i] != "") shares_a_row[named[i]] = 1;
|
||||
}
|
||||
}
|
||||
awk -F, '
|
||||
{
|
||||
# Combine the modifier and key (first two fields)
|
||||
key_combo = $1 " + " $2;
|
||||
@@ -398,35 +365,8 @@ BEGIN {
|
||||
gsub(/[ \t]+/, " ", key_combo); # Collapse multiple spaces to one
|
||||
}
|
||||
|
||||
# An alternative chord joins the row the first one opened: "SUPER + W /
|
||||
# SUPER + Q -> Close window". Both halves have to agree on what they run,
|
||||
# since a label is only what a chord is called; an unresolved dispatcher
|
||||
# says nothing at all, so it never counts as agreement.
|
||||
if (action != "") {
|
||||
action_key = action SUBSEP dispatcher SUBSEP arg;
|
||||
together = "";
|
||||
|
||||
if (action in shares_a_row && dispatcher != "" && action_key in leads) {
|
||||
together = chords[leads[action_key]] " / " key_combo;
|
||||
}
|
||||
|
||||
if (together != "" && length(together) <= column) {
|
||||
chords[leads[action_key]] = together;
|
||||
} else {
|
||||
# Also the path a pair too wide for the column takes: two rows in
|
||||
# line beat one that juts out of it.
|
||||
entries++;
|
||||
chords[entries] = key_combo;
|
||||
actions[entries] = action;
|
||||
dispatchers[entries] = dispatcher;
|
||||
args[entries] = arg;
|
||||
leads[action_key] = entries;
|
||||
}
|
||||
}
|
||||
}
|
||||
END {
|
||||
for (entry = 1; entry <= entries; entry++) {
|
||||
printf "%-*s → %s\t%s\t%s\n", column, chords[entry], actions[entry], dispatchers[entry], args[entry];
|
||||
printf "%-35s → %s\t%s\t%s\n", key_combo, action, dispatcher, arg;
|
||||
}
|
||||
}'
|
||||
}
|
||||
@@ -434,13 +374,7 @@ END {
|
||||
prioritize_entries() {
|
||||
awk -F '\t' '
|
||||
{
|
||||
# Alternative chords are display only. Classifying on them would read
|
||||
# "SUPER SHIFT + RETURN / SUPER SHIFT + B" as the alternate browser chord
|
||||
# and drop the browser 20 rows down its own list, so match the chord that
|
||||
# leads the row and ignore the rest.
|
||||
line = $1
|
||||
sub(/ \/ [^→]*→/, " →", line)
|
||||
|
||||
prio = 50
|
||||
if (match(line, /Keybindings/)) prio = 0
|
||||
if (match(line, /Omarchy menu/)) prio = 1
|
||||
@@ -502,12 +436,6 @@ prioritize_entries() {
|
||||
cut -f2-
|
||||
}
|
||||
|
||||
# Drop repeated records while keeping the order Hyprland reported them in, so
|
||||
# the chord a user declared first is the one that leads a merged entry.
|
||||
dedupe_binding_records() {
|
||||
awk '!seen[$0]++'
|
||||
}
|
||||
|
||||
output_binding_records_uncached() {
|
||||
local dynamic
|
||||
|
||||
@@ -518,7 +446,7 @@ output_binding_records_uncached() {
|
||||
[[ -n $dynamic ]] && printf '%s\n' "$dynamic"
|
||||
static_bindings
|
||||
} |
|
||||
dedupe_binding_records |
|
||||
sort -u |
|
||||
parse_keycodes |
|
||||
parse_binding_records |
|
||||
prioritize_entries
|
||||
@@ -530,7 +458,7 @@ output_binding_records_uncached() {
|
||||
|
||||
keybindings_cache_key() {
|
||||
{
|
||||
printf 'v13\n'
|
||||
printf 'v11\n'
|
||||
hyprctl devices 2>/dev/null | grep -F 'active keymap:'
|
||||
hyprctl binds 2>/dev/null
|
||||
} | sha256sum | awk '{ print $1 }'
|
||||
|
||||
@@ -12,8 +12,38 @@ package=$1
|
||||
command=${2:-$1}
|
||||
bin=${3:-$command}
|
||||
|
||||
# The command name becomes a file name under ~/.local/bin, so a slash in it
|
||||
# writes the wrapper somewhere else and the rm below deletes somewhere else. A
|
||||
# leading dot hides it or walks up, and a leading dash makes a name that reads
|
||||
# as an option to whatever picks it up. Checked before anything is removed or
|
||||
# written, and kept to those shapes so package names like npm:playwright still
|
||||
# stand in for the command name.
|
||||
case "$command" in
|
||||
*/* | .* | -* | *[[:cntrl:]]*)
|
||||
echo "omarchy-mise-install: '$command' is not usable as a command name" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
mkdir -p "$HOME/.local/bin"
|
||||
|
||||
# The heredoc below is unquoted, so whatever these hold is written into the
|
||||
# wrapper as shell source. Quote them the way omarchy-install-and-launch does, so
|
||||
# a package name carrying shell characters stays one argument instead of running.
|
||||
printf -v package_arg '%q' "$package"
|
||||
printf -v bin_arg '%q' "$bin"
|
||||
|
||||
# Keep values that are inert inside double quotes in the form existing migrations
|
||||
# recognize, including mise backend options that %q would unnecessarily escape.
|
||||
case "$package" in
|
||||
*'$'* | *'`'* | *'"'* | *'\'* | *'!'* | *[[:cntrl:]]*) ;;
|
||||
*) package_arg="\"$package\"" ;;
|
||||
esac
|
||||
case "$bin" in
|
||||
*'$'* | *'`'* | *'"'* | *'\'* | *'!'* | *[[:cntrl:]]*) ;;
|
||||
*) bin_arg="\"$bin\"" ;;
|
||||
esac
|
||||
|
||||
# These tools install and upgrade on first run, so mise's release cooldown would
|
||||
# hold a new version back for days after it ships. Exported rather than set on
|
||||
# the install line alone, so resolving the version to execute agrees with the
|
||||
@@ -22,8 +52,8 @@ rm -f "$HOME/.local/bin/$command"
|
||||
cat >"$HOME/.local/bin/$command" <<EOF
|
||||
#!/bin/bash
|
||||
export MISE_MINIMUM_RELEASE_AGE=0
|
||||
mise use -g --quiet "$package" || exit 1
|
||||
exec mise x "$package" -- "$bin" "\$@"
|
||||
mise use -g --quiet $package_arg || exit 1
|
||||
exec mise x $package_arg -- $bin_arg "\$@"
|
||||
EOF
|
||||
|
||||
chmod +x "$HOME/.local/bin/$command"
|
||||
@@ -34,18 +34,23 @@ print_status() {
|
||||
return
|
||||
fi
|
||||
|
||||
nm=$(nmcli -t -f GENERAL.STATE,GENERAL.CONNECTION dev show "$device" 2>/dev/null)
|
||||
state=$(awk -F: '$1 == "GENERAL.STATE" { print $2; exit }' <<<"$nm")
|
||||
ssid=$(awk -F: '$1 == "GENERAL.CONNECTION" { print $2; exit }' <<<"$nm")
|
||||
signal=$(nmcli -t -f IN-USE,SIGNAL dev wifi list ifname "$device" --rescan no 2>/dev/null | awk -F: '$1 == "*" { print $2; exit }')
|
||||
freq=$(iw dev "$device" link 2>/dev/null | awk '/freq:/ { print $2; exit }')
|
||||
if omarchy-cmd-present nmcli; then
|
||||
nm=$(nmcli -t -f GENERAL.STATE,GENERAL.CONNECTION dev show "$device" 2>/dev/null)
|
||||
state=$(awk -F: '$1 == "GENERAL.STATE" { print $2; exit }' <<<"$nm")
|
||||
ssid=$(awk -F: '$1 == "GENERAL.CONNECTION" { print $2; exit }' <<<"$nm")
|
||||
signal=$(nmcli -t -f IN-USE,SIGNAL dev wifi list ifname "$device" --rescan no 2>/dev/null | awk -F: '$1 == "*" { print $2; exit }')
|
||||
freq=$(iw dev "$device" link 2>/dev/null | awk '/freq:/ { print $2; exit }')
|
||||
|
||||
if [[ $state != 100* ]]; then
|
||||
printf 'disconnected\t\t\t\n'
|
||||
if [[ $state != 100* ]]; then
|
||||
printf 'disconnected\t\t\t\n'
|
||||
return
|
||||
fi
|
||||
|
||||
printf 'wifi\t%s\t%s\t%s\n' "${ssid:-$device}" "$signal" "$freq"
|
||||
return
|
||||
fi
|
||||
|
||||
printf 'wifi\t%s\t%s\t%s\n' "${ssid:-$device}" "$signal" "$freq"
|
||||
printf 'wifi\t%s\t\t\n' "$device"
|
||||
}
|
||||
|
||||
ping_latency_ms() {
|
||||
|
||||
Executable
+130
@@ -0,0 +1,130 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Run OpenClaw's setup wizard the way Omarchy needs it: in the terminal, installing the gateway as a user service, and returning when it is done.
|
||||
|
||||
# Not bare `openclaw onboard`: as of 2026.9.1 that is the guided flow, which
|
||||
# ends by running a foreground gateway and handing off to a browser tab, and
|
||||
# never returns. --install-daemon keeps it to the classic wizard (minimal
|
||||
# prompts with --flow quickstart) that installs the gateway service, and
|
||||
# --skip-ui drops its closing Control UI/TUI prompt since whoever called this
|
||||
# opens one next.
|
||||
#
|
||||
# That classic wizard has one wrinkle: with --skip-ui it prints "Onboarding
|
||||
# complete" and then never exits. Upstream only calls exit(0) when it launched
|
||||
# the TUI, and the model sign-in leaves an open socket that keeps the process
|
||||
# alive otherwise. So this script watches for the gateway to answer and then
|
||||
# stops the wizard. That is safe because every prompt in the quickstart flow
|
||||
# runs before the gateway service is installed and reachable: by the time the
|
||||
# dashboard answers, only the closing notes are left.
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
wizard=(openclaw onboard --flow quickstart --install-daemon --skip-ui)
|
||||
config=$HOME/.openclaw/openclaw.json
|
||||
settle_seconds=${OMARCHY_OPENCLAW_ONBOARD_SETTLE_SECONDS:-3}
|
||||
# Counted from the moment the config exists, i.e. once the wizard has applied
|
||||
# setup; the prompts before that take as long as the user takes.
|
||||
gateway_timeout=${OMARCHY_OPENCLAW_ONBOARD_GATEWAY_TIMEOUT:-180}
|
||||
|
||||
gateway_answers() {
|
||||
timeout 10 openclaw dashboard --json 2>/dev/null | jq -e '.ok == true' >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# A gateway already answering means OpenClaw is set up, and this run must not
|
||||
# read its own success off state that predates it: the wizard's repair pass
|
||||
# would be stopped mid-prompt the moment the watcher looked. Nothing to do.
|
||||
if [[ -f $config ]] && gateway_answers; then
|
||||
echo "OpenClaw is already set up and its gateway is running." >&2
|
||||
echo "To change providers or settings, run: openclaw onboard --classic" >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Marks when this run began, so a config left behind by an earlier, incomplete
|
||||
# setup is not mistaken for this run having applied its own: the gateway
|
||||
# deadline below must not start ticking while the user is still at prompts.
|
||||
started=$(mktemp)
|
||||
trap 'rm -f "$started"' EXIT
|
||||
|
||||
# Backgrounded so this script can watch it, but with the terminal kept as its
|
||||
# stdin (bash would otherwise hand a background job /dev/null). Job control is
|
||||
# off in a script, so it stays in the terminal's foreground process group and
|
||||
# reads from it freely. Ctrl-C does not reach it directly, though: bash starts
|
||||
# async children with SIGINT ignored when job control is off, so the INT trap
|
||||
# below is what turns Ctrl-C into the wizard's exit.
|
||||
"${wizard[@]}" <&0 &
|
||||
wizard_pid=$!
|
||||
|
||||
stop_wizard() {
|
||||
kill -TERM "$wizard_pid" 2>/dev/null || true
|
||||
}
|
||||
# Any signal at this script, whether Ctrl-C from the terminal or a kill aimed
|
||||
# at its pid alone, takes the wizard down with it rather than leaving it
|
||||
# running unwatched.
|
||||
trap 'stop_wizard' INT TERM HUP
|
||||
|
||||
# Whether this run has applied setup: the config exists and is not older than
|
||||
# the run itself.
|
||||
config_applied() {
|
||||
[[ -f $config && ! $started -nt $config ]]
|
||||
}
|
||||
|
||||
# Whether this run's gateway is up: setup applied, and the process answering
|
||||
# on the gateway's port is the main process of the service the wizard
|
||||
# installs. Not the dashboard alone: with no config on disk `openclaw
|
||||
# dashboard --json` still probes the default loopback port, so a gateway left
|
||||
# behind by something else (an earlier guided onboarding's foreground
|
||||
# gateway, say) would read as this run's success while the user is still at
|
||||
# the first prompt. And not the unit being active either: its Type=simple
|
||||
# counts it active from the fork, before it has found the port taken by such
|
||||
# an orphan, and the app would then open on the orphan rather than the
|
||||
# service this run installed.
|
||||
gateway_ready() {
|
||||
config_applied || return 1
|
||||
local json port listener main_pid
|
||||
json=$(timeout 10 openclaw dashboard --json 2>/dev/null) || return 1
|
||||
jq -e '.ok == true' <<<"$json" >/dev/null 2>&1 || return 1
|
||||
port=$(jq -r '.port // empty' <<<"$json" 2>/dev/null)
|
||||
[[ -n $port ]] || return 1
|
||||
listener=$(ss -ltnpH "sport = :$port" 2>/dev/null | sed -n 's/.*pid=\([0-9]*\).*/\1/p' | head -1)
|
||||
main_pid=$(systemctl --user show -p MainPID --value openclaw-gateway.service 2>/dev/null)
|
||||
[[ -n $listener && -n $main_pid && $main_pid != 0 && $listener == "$main_pid" ]]
|
||||
}
|
||||
|
||||
stopped=false
|
||||
timed_out=false
|
||||
config_seen_at=
|
||||
while kill -0 "$wizard_pid" 2>/dev/null; do
|
||||
sleep 2
|
||||
if gateway_ready; then
|
||||
# Let the outro finish printing, then end the process the wizard leaves
|
||||
# running.
|
||||
sleep "$settle_seconds"
|
||||
stop_wizard
|
||||
stopped=true
|
||||
break
|
||||
fi
|
||||
config_applied || continue
|
||||
: "${config_seen_at:=$SECONDS}"
|
||||
if (( SECONDS - config_seen_at >= gateway_timeout )); then
|
||||
# Setup was applied but the service never came up (port taken, unit
|
||||
# failing, ...): the wizard would sit in its never-exiting state forever,
|
||||
# and so would whoever is waiting on this script.
|
||||
stop_wizard
|
||||
timed_out=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
wait "$wizard_pid"
|
||||
rc=$?
|
||||
|
||||
if [[ $timed_out == true ]]; then
|
||||
echo "OpenClaw's gateway did not come up within ${gateway_timeout}s of setup finishing." >&2
|
||||
echo "Check it with: openclaw gateway status" >&2
|
||||
exit 1
|
||||
fi
|
||||
# A wizard stopped here after the gateway came up did its job. One that
|
||||
# exited on its own, including a user who chose "Skip for now" (no config,
|
||||
# non-zero), keeps its own exit code.
|
||||
[[ $stopped == true ]] && rc=0
|
||||
exit "$rc"
|
||||
@@ -24,6 +24,6 @@ if [[ -n $pkg_names ]]; then
|
||||
source omarchy-sudo-keepalive
|
||||
|
||||
echo "$pkg_names" | sed 's/^/aur\//' | tr '\n' ' ' | xargs yay -S --noconfirm
|
||||
sudo updatedb
|
||||
sudo updatedb --prune-bind-mounts=no --add-prunepaths=/.snapshots
|
||||
omarchy-show-done
|
||||
fi
|
||||
@@ -677,11 +677,15 @@ user_groups() {
|
||||
if [[ -f $PROVISIONING_DIR/groups ]]; then
|
||||
while IFS= read -r group; do
|
||||
[[ -n $group ]] || continue
|
||||
# Never grant docker at first boot, even if an older install recorded it
|
||||
# (or a factory snapshot predating the opt-in default carries it): the
|
||||
# docker group is root-equivalent. It is opt-in via
|
||||
# omarchy-setup-security-sudoless-docker.
|
||||
# Never replay old privileged group defaults. Docker is always opt-in.
|
||||
# Input is only retained when the factory image has one of the features
|
||||
# whose installer deliberately grants access to raw input devices.
|
||||
[[ $group == "docker" ]] && continue
|
||||
if [[ $group == "input" ]] &&
|
||||
! pacman -Qq xpadneo-dkms &>/dev/null &&
|
||||
! pacman -Qq ydotool &>/dev/null; then
|
||||
continue
|
||||
fi
|
||||
getent group "$group" >/dev/null || continue
|
||||
[[ ",$groups," == *",$group,"* ]] || groups+=",$group"
|
||||
done <"$PROVISIONING_DIR/groups"
|
||||
|
||||
@@ -84,7 +84,7 @@ fi
|
||||
# Dev-aware skill symlinks. Cannot live in /etc/skel because OMARCHY_PATH may
|
||||
# point at a dev checkout (omarchy dev link) where the target differs.
|
||||
# Loops every skill directory, so shipping a new one needs no edit here.
|
||||
mkdir -p ~/.agents/skills ~/.claude/skills ~/.codex/skills ~/.pi/agent/skills ~/.gemini/config/skills
|
||||
mkdir -p ~/.agents/skills ~/.claude/skills ~/.codex/skills ~/.pi/agent/skills ~/.hermes/skills
|
||||
for skill in "$OMARCHY_PATH"/default/agents/skills/*/; do
|
||||
skill=${skill%/}
|
||||
name=${skill##*/}
|
||||
@@ -92,7 +92,14 @@ for skill in "$OMARCHY_PATH"/default/agents/skills/*/; do
|
||||
ln -sfn "$skill" ~/.claude/skills/"$name"
|
||||
ln -sfn "$skill" ~/.codex/skills/"$name"
|
||||
ln -sfn "$skill" ~/.pi/agent/skills/"$name"
|
||||
ln -sfn "$skill" ~/.gemini/config/skills/"$name"
|
||||
ln -sfn "$skill" ~/.hermes/skills/"$name"
|
||||
if [[ -d ~/.hermes/profiles ]]; then
|
||||
for profile in ~/.hermes/profiles/*/; do
|
||||
[[ -d $profile ]] || continue
|
||||
mkdir -p "$profile/skills"
|
||||
ln -sfn "$skill" "$profile/skills/$name"
|
||||
done
|
||||
fi
|
||||
done
|
||||
|
||||
mkdir -p ~/Downloads ~/Pictures ~/Videos ~/.config/gtk-3.0
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Remove the ChatGPT desktop app along with its configuration and caches.
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
set -e
|
||||
|
||||
omarchy-pkg-drop openai-codex-desktop
|
||||
|
||||
# Not ~/.cache/codex-runtimes: the Codex CLI resolves its own runtime under
|
||||
# there, and it ships in a different package that survives this one.
|
||||
rm -rf \
|
||||
"$HOME/.config/Codex" \
|
||||
"$HOME/.cache/Codex"
|
||||
|
||||
echo ""
|
||||
echo "ChatGPT has been removed."
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Remove Grok Bot along with its settings and data.
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
set -e
|
||||
|
||||
omarchy-pkg-drop grok-bot
|
||||
|
||||
# Not ~/.grok: that is the Grok CLI, which is installed and used on its own.
|
||||
rm -rf \
|
||||
"$HOME/.config/Grok Bot" \
|
||||
"$HOME/.grokbot"
|
||||
|
||||
echo ""
|
||||
echo "Grok Bot has been removed."
|
||||
Executable
+147
@@ -0,0 +1,147 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Remove the Hermes desktop app along with the Hermes runtime it installed.
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
# -u so an unset HOME is an error rather than a set of rm -rf paths rooted at /.
|
||||
set -euo pipefail
|
||||
|
||||
ensure_hermes_stopped() {
|
||||
python3 - "$HOME" <<'PY'
|
||||
import os
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
home = Path(sys.argv[1])
|
||||
roots = [str((home / relative).resolve()) for relative in ('.hermes', '.config/Hermes')]
|
||||
roots.append('/opt/hermes-desktop')
|
||||
|
||||
def belongs_to_hermes(target):
|
||||
target = target.removesuffix(' (deleted)')
|
||||
return any(target == root or target.startswith(root + '/') for root in roots)
|
||||
|
||||
holders = []
|
||||
for process in Path('/proc').iterdir():
|
||||
if not process.name.isdigit() or int(process.name) == os.getpid():
|
||||
continue
|
||||
try:
|
||||
if process.stat().st_uid != os.getuid():
|
||||
continue
|
||||
targets = [os.fsdecode(arg) for arg in (process / 'cmdline').read_bytes().split(b'\0')]
|
||||
entries = [process / 'exe', process / 'cwd']
|
||||
try:
|
||||
entries.extend((process / 'fd').iterdir())
|
||||
except PermissionError:
|
||||
pass
|
||||
for entry in entries:
|
||||
try:
|
||||
targets.append(os.readlink(entry))
|
||||
except OSError:
|
||||
pass
|
||||
if any(belongs_to_hermes(target) for target in targets):
|
||||
holders.append(process.name)
|
||||
except (FileNotFoundError, ProcessLookupError, PermissionError):
|
||||
continue
|
||||
|
||||
if holders:
|
||||
print('Close Hermes and processes using its files before removing it (PIDs: '
|
||||
+ ', '.join(holders) + '). Then try again.', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
PY
|
||||
}
|
||||
|
||||
# Removing an open SQLite WAL leaves a live writer on a deleted generation.
|
||||
ensure_hermes_stopped
|
||||
omarchy-pkg-drop hermes-desktop
|
||||
|
||||
# The installer leaves a unit waiting to hand the app the Omarchy theme.
|
||||
systemctl --user stop omarchy-hermes-theme.service 2>/dev/null || true
|
||||
|
||||
# The mise CLI is the app's predecessor, not the app itself: Hermes Desktop takes
|
||||
# it over on install and runs its own runtime instead, so a copy still here is one
|
||||
# the app never superseded -- an interrupted install, or the terminal CLI from
|
||||
# before the app existed. Remove Hermes clears that too, scoped by the installer
|
||||
# to what Omarchy owns so a hermes the user set up themselves is left alone.
|
||||
# Tolerated here rather than fatal, so the ~/.hermes handling below still runs;
|
||||
# the failure is answered for at the end instead of being swallowed.
|
||||
cli_removed=true
|
||||
ensure_hermes_stopped
|
||||
omarchy-install-hermes-cli --remove || cli_removed=false
|
||||
|
||||
# The app writes this when the runtime it provisions under ~/.hermes has landed,
|
||||
# and it is the only thing that tells that runtime apart from one the user
|
||||
# installed themselves -- the paths are the same either way. Without it the app
|
||||
# never got that far: a machine where it was installed but never launched still
|
||||
# has whatever was there before, and none of it is ours to delete unasked.
|
||||
if [[ -f $HOME/.hermes/hermes-agent/.hermes-bootstrap-complete ]]; then
|
||||
# The checkout and venv, its own uv, its own node. None of it is any use once
|
||||
# the app is gone, so it goes without asking; what the user made with the app
|
||||
# is a different question, answered below.
|
||||
rm -rf \
|
||||
"$HOME/.hermes/hermes-agent" \
|
||||
"$HOME/.hermes/bootstrap-cache" \
|
||||
"$HOME/.hermes/bin" \
|
||||
"$HOME/.hermes/node"
|
||||
|
||||
# Only the wrappers pointing into ~/.hermes, matched as a plain string: the
|
||||
# path carries a dot, so an unanchored pattern would also claim a wrapper
|
||||
# pointing at a sibling like ~/xhermes.
|
||||
for command in hermes hermes-agent hermes-acp; do
|
||||
wrapper="$HOME/.local/bin/$command"
|
||||
|
||||
if [[ -f $wrapper && ! -L $wrapper ]] && grep -qF "$HOME/.hermes" "$wrapper"; then
|
||||
rm -f "$wrapper"
|
||||
fi
|
||||
done
|
||||
|
||||
# When Hermes brought its own Node it symlinked these next to its own commands,
|
||||
# and they point at what we just deleted. Only the links into ~/.hermes: a
|
||||
# system Node, or someone else's, lives somewhere else entirely.
|
||||
for command in node npm npx; do
|
||||
link="$HOME/.local/bin/$command"
|
||||
|
||||
if [[ -L $link && $(readlink "$link") == "$HOME/.hermes"/* ]]; then
|
||||
rm -f "$link"
|
||||
fi
|
||||
done
|
||||
|
||||
fi
|
||||
|
||||
# What survives to here is the user's: the chats, memories and skills in
|
||||
# ~/.hermes, the connections and their encrypted tokens in ~/.config/Hermes.
|
||||
# Keeping them stays the default -- they are small, and finding them intact
|
||||
# after a reinstall is the better surprise -- but a removal meant to be
|
||||
# complete should not leave credentials behind either, so the choice is put in
|
||||
# front of the user with the size, default no. Asked whenever the directories
|
||||
# exist, marker or no marker: on a machine where the marker never appeared the
|
||||
# data came from the terminal CLI or an install the app never finished, and it
|
||||
# is still what removal is asked to clean up. Naming the paths keeps the
|
||||
# question honest there too -- ~/.hermes may still carry a runtime the app
|
||||
# never owned, a yes takes that with it, and saying so is the prompt's job.
|
||||
# Without a terminal to ask in, keeping everything is the answer.
|
||||
data_removed=false
|
||||
if [[ -d $HOME/.hermes || -d $HOME/.config/Hermes ]] && [[ -t 0 ]] && omarchy-cmd-present gum; then
|
||||
# du answers non-zero when either directory is missing, and pipefail would
|
||||
# turn that into an aborted removal; the size is worth no such thing.
|
||||
size=$(du -shc "$HOME/.hermes" "$HOME/.config/Hermes" 2>/dev/null | tail -1 | cut -f1 || true)
|
||||
if gum confirm --default=false "Also delete ~/.hermes and ~/.config/Hermes ($size: chats, memories, skills, connections and tokens)?"; then
|
||||
ensure_hermes_stopped
|
||||
rm -rf "$HOME/.hermes" "$HOME/.config/Hermes"
|
||||
data_removed=true
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Hermes Desktop has been removed."
|
||||
if [[ $data_removed == true ]]; then
|
||||
echo "Its chats, memories, and settings in ~/.hermes and ~/.config/Hermes are gone too."
|
||||
elif [[ -d $HOME/.hermes || -d $HOME/.config/Hermes ]]; then
|
||||
echo "Your chats, memories, and skills are still in ~/.hermes,"
|
||||
echo "and your connections and settings in ~/.config/Hermes."
|
||||
fi
|
||||
|
||||
# The messages above still hold -- the app and its runtime are gone -- but a CLI
|
||||
# teardown that failed already said so on stderr, and that stands.
|
||||
if [[ $cli_removed == "false" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,29 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Remove LM Studio along with its configuration and every model it downloaded.
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
set -e
|
||||
|
||||
omarchy-pkg-drop lmstudio-bin
|
||||
|
||||
# LM Studio's home is relocatable and the pointer is the only record of where
|
||||
# the models went, so it has to be read before it is deleted with everything else.
|
||||
lmstudio_home=""
|
||||
if [[ -f $HOME/.lmstudio-home-pointer ]]; then
|
||||
read -r lmstudio_home <"$HOME/.lmstudio-home-pointer" || true
|
||||
fi
|
||||
|
||||
rm -rf \
|
||||
"$HOME/.config/LM Studio" \
|
||||
"$HOME/.config/LM-Studio" \
|
||||
"$HOME/.lmstudio" \
|
||||
"$HOME/.lmstudio-home-pointer"
|
||||
|
||||
if [[ -n $lmstudio_home && $lmstudio_home == /* && -d $lmstudio_home ]] &&
|
||||
[[ $lmstudio_home != "/" && $lmstudio_home != "$HOME" ]]; then
|
||||
rm -rf "$lmstudio_home"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "LM Studio and its models have been removed."
|
||||
@@ -1,18 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Remove Ollama along with every model it pulled.
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
set -e
|
||||
|
||||
# The service is what holds the models open, so it goes before the package.
|
||||
sudo systemctl disable --now ollama.service 2>/dev/null || true
|
||||
|
||||
# Whichever acceleration variant is installed provides the same command.
|
||||
omarchy-pkg-drop ollama ollama-cuda ollama-rocm ollama-vulkan
|
||||
|
||||
sudo rm -rf /var/lib/ollama
|
||||
rm -rf "$HOME/.ollama"
|
||||
|
||||
echo ""
|
||||
echo "Ollama and its models have been removed."
|
||||
Executable
+78
@@ -0,0 +1,78 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Remove the OpenClaw agent platform along with its gateway service and web app.
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
# -u so an unset HOME is an error rather than a set of rm -rf paths rooted at /.
|
||||
set -euo pipefail
|
||||
|
||||
unit_dir="$HOME/.config/systemd/user"
|
||||
|
||||
# Only systemd's own word counts as "stopped": a non-zero exit from is-active
|
||||
# also covers an unreachable user manager, which says nothing about whether
|
||||
# the process is alive.
|
||||
unit_stopped() {
|
||||
local state
|
||||
state=$(systemctl --user is-active "$1" 2>/dev/null) || true
|
||||
[[ $state == inactive || $state == failed ]]
|
||||
}
|
||||
|
||||
# The user services OpenClaw installs for itself: the gateway through
|
||||
# onboarding, and the node host if the user ever paired this machine to
|
||||
# another gateway. OpenClaw writes them to $HOME/.config regardless of
|
||||
# XDG_CONFIG_HOME, so look exactly there; no unit means nothing registered.
|
||||
# Upstream's own teardown knows every piece its install wrote (unit file, the
|
||||
# default.target.wants enablement symlink, the reload), so prefer it while the
|
||||
# binary is still installed and fall back to doing the same by hand. A service
|
||||
# that will not stop aborts the removal: dropping the package would strand the
|
||||
# live process on deleted code with no way to restart it cleanly.
|
||||
for unit_file in "$unit_dir"/openclaw-gateway.service "$unit_dir"/openclaw-node.service; do
|
||||
[[ -f $unit_file ]] || continue
|
||||
unit=${unit_file##*/}
|
||||
role=${unit#openclaw-}
|
||||
role=${role%.service}
|
||||
if openclaw "$role" uninstall >/dev/null 2>&1 ||
|
||||
systemctl --user disable --now "$unit" 2>/dev/null ||
|
||||
unit_stopped "$unit"; then
|
||||
# .bak is what `gateway install --force` leaves behind when it rewrites a
|
||||
# unit, so it goes with the unit.
|
||||
rm -f "$unit_file" "$unit_file.bak" "$unit_dir/default.target.wants/$unit"
|
||||
systemctl --user daemon-reload 2>/dev/null || true
|
||||
# A unit that had been failing stays listed as "not-found failed" after
|
||||
# its file is gone until its failed state is reset.
|
||||
systemctl --user reset-failed "$unit" 2>/dev/null || true
|
||||
else
|
||||
echo "Could not stop $unit; OpenClaw was not removed." >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
omarchy-pkg-drop openclaw
|
||||
|
||||
# The web app launcher and icon omarchy-install-ai-openclaw created.
|
||||
rm -f "$HOME/.local/share/applications/OpenClaw.desktop"
|
||||
rm -f "$HOME/.local/share/icons/hicolor/256x256/apps/openclaw.png"
|
||||
gtk-update-icon-cache "$HOME/.local/share/icons/hicolor" &>/dev/null || true
|
||||
|
||||
# ~/.openclaw stays unless asked: the chats, memories, and credentials in
|
||||
# there are the user's agent, and finding them intact after a reinstall is the
|
||||
# better surprise. But it also holds the plugin runtimes and caches OpenClaw
|
||||
# downloads for itself, easily hundreds of megabytes, so the choice is put in
|
||||
# front of the user with the size rather than left silent. Without a terminal
|
||||
# to ask in, keeping it is the answer.
|
||||
state_removed=false
|
||||
if [[ -d $HOME/.openclaw && -t 0 ]] && omarchy-cmd-present gum; then
|
||||
size=$(du -sh "$HOME/.openclaw" 2>/dev/null | cut -f1)
|
||||
if gum confirm --default=false "Also delete ~/.openclaw ($size: chats, memories, credentials, and downloaded plugins)?"; then
|
||||
rm -rf "$HOME/.openclaw"
|
||||
state_removed=true
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "OpenClaw has been removed."
|
||||
if [[ $state_removed == true ]]; then
|
||||
echo "Its chats, memories, and settings in ~/.openclaw are gone too."
|
||||
elif [[ -d $HOME/.openclaw ]]; then
|
||||
echo "Your agent's chats, memories, and settings are still in ~/.openclaw."
|
||||
fi
|
||||
Executable
+49
@@ -0,0 +1,49 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Remove the Perplexity desktop app along with its runtime caches.
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
# -u so an unset HOME is an error rather than a set of rm -rf paths rooted at /.
|
||||
set -euo pipefail
|
||||
|
||||
omarchy-pkg-drop perplexity
|
||||
|
||||
# The runtime the app downloads for itself: llama.cpp builds and local models
|
||||
# under ~/.local/share, the older download location under ~/.cache. Of the
|
||||
# perplexity-* dirs these are the only ones the desktop app owns; the rest
|
||||
# belong to Perplexity products that outlive it.
|
||||
rm -rf \
|
||||
"$HOME/.cache/Perplexity" \
|
||||
"$HOME/.cache/perplexity-rpc-server" \
|
||||
"$HOME/.local/share/perplexity-rpc-server"
|
||||
|
||||
# What is left is the user's: the logins and session in ~/.config/Perplexity,
|
||||
# the secret vault and device identity in ~/.local/state/perplexity, and the
|
||||
# launcher flags. Keeping them stays the default -- they are small, and being
|
||||
# signed in after a reinstall is the better surprise -- but a removal meant to
|
||||
# be complete should not leave credentials behind either, so the choice is put
|
||||
# in front of the user, default no. Without a terminal to ask in, keeping it
|
||||
# is the answer -- and gum draws the prompt on stderr, so a redirected stderr
|
||||
# would block on a question nobody can see.
|
||||
data_removed=false
|
||||
if [[ -t 0 && -t 2 ]]; then
|
||||
# du answers non-zero when a directory is missing, and pipefail would turn
|
||||
# that into an aborted removal; the size is worth no such thing.
|
||||
size=$(du -shc "$HOME/.config/Perplexity" "$HOME/.local/state/perplexity" 2>/dev/null | tail -1 | cut -f1 || true)
|
||||
if gum confirm --default=false "Also delete your Perplexity data ($size: logins, settings, secret vault and device identity)?"; then
|
||||
rm -rf \
|
||||
"$HOME/.config/Perplexity" \
|
||||
"$HOME/.local/state/perplexity"
|
||||
rm -f "$HOME/.config/perplexity-flags.conf"
|
||||
data_removed=true
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Perplexity has been removed."
|
||||
if [[ $data_removed == "true" ]]; then
|
||||
echo "Its logins, settings, secret vault, and device identity are gone too."
|
||||
else
|
||||
echo "Nothing of yours was touched: not the logins in ~/.config/Perplexity,"
|
||||
echo "nor the secret vault and device identity in ~/.local/state/perplexity."
|
||||
fi
|
||||
@@ -10,7 +10,7 @@ if [[ -z $1 ]]; then
|
||||
fi
|
||||
|
||||
remove_php() {
|
||||
omarchy-pkg-drop php composer php-sqlite xdebug
|
||||
sudo pacman -Rns --noconfirm php composer php-sqlite xdebug 2>/dev/null || true
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
@@ -50,7 +50,7 @@ laravel)
|
||||
;;
|
||||
symfony)
|
||||
echo -e "Removing Symfony CLI...\n"
|
||||
omarchy-pkg-drop symfony-cli
|
||||
sudo pacman -Rns --noconfirm symfony-cli 2>/dev/null || true
|
||||
;;
|
||||
python)
|
||||
echo -e "Removing Python...\n"
|
||||
|
||||
@@ -13,10 +13,30 @@ if gum confirm "Are you sure you want to remove all preinstalled web apps, TUI w
|
||||
hyprctl reload
|
||||
|
||||
# Remove mise stubs
|
||||
rm -f ~/.local/bin/codex ~/.local/bin/claude ~/.local/bin/agy ~/.local/bin/copilot \
|
||||
rm -f ~/.local/bin/codex ~/.local/bin/claude ~/.local/bin/gemini ~/.local/bin/copilot \
|
||||
~/.local/bin/gh ~/.local/bin/opencode ~/.local/bin/playwright ~/.local/bin/playwright-cli ~/.local/bin/pi \
|
||||
~/.local/bin/omp ~/.local/bin/ori ~/.local/bin/grok ~/.local/bin/crush ~/.local/bin/ghui ~/.local/bin/hunk \
|
||||
~/.local/bin/dim ~/.local/bin/kimi
|
||||
~/.local/bin/omp ~/.local/bin/grok ~/.local/bin/crush ~/.local/bin/ghui ~/.local/bin/hunk
|
||||
|
||||
# Cursor's own installer links ~/.local/bin/cursor-agent as well, so only
|
||||
# the mise wrapper omarchy-mise-install wrote is a preinstall.
|
||||
if [[ -f ~/.local/bin/cursor-agent && ! -L ~/.local/bin/cursor-agent ]] &&
|
||||
grep -Eq '^mise use -g .*"cursor-agent"' ~/.local/bin/cursor-agent; then
|
||||
rm -f ~/.local/bin/cursor-agent
|
||||
fi
|
||||
|
||||
# Preserve a user-managed Muse launcher at the same path.
|
||||
if [[ -f ~/.local/bin/muse && ! -L ~/.local/bin/muse ]] &&
|
||||
grep -Eq '^mise use -g .*"http:muse\[' ~/.local/bin/muse; then
|
||||
rm -f ~/.local/bin/muse
|
||||
fi
|
||||
|
||||
# Only the wrapper omarchy-install-hermes-cli wrote is a preinstall. Hermes
|
||||
# Desktop's command, an official install, or anything else at that path is
|
||||
# the user's, so it is the installer that decides whether the wrapper is its
|
||||
# own, rather than a copy of its marker kept here.
|
||||
if omarchy-install-hermes-cli --owns; then
|
||||
rm -f ~/.local/bin/hermes
|
||||
fi
|
||||
|
||||
omarchy-pkg-drop \
|
||||
aether \
|
||||
|
||||
@@ -74,18 +74,15 @@ if ! omarchy-hw-fingerprint; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Install required packages
|
||||
echo "Installing required packages..."
|
||||
|
||||
# libfprint-git provides+conflicts libfprint; pacman -S --noconfirm
|
||||
# defaults the conflict prompt to N and aborts. Pre-remove it (deps-only,
|
||||
# so an installed fprintd stays put) so stock libfprint installs cleanly.
|
||||
if pacman -Q libfprint-git &>/dev/null; then
|
||||
sudo pacman -Rdd --noconfirm libfprint-git
|
||||
# libfprint-git tracks upstream ahead of the Arch release, so a new reader only
|
||||
# needs a pin bump in omarchy-pkgs. It conflicts with stock libfprint, and
|
||||
# --noconfirm answers that prompt with N; --ask 4 accepts the replacement in
|
||||
# one transaction, so a failed install leaves the existing driver in place.
|
||||
if omarchy-pkg-missing libfprint-git fprintd usbutils; then
|
||||
echo "Installing required packages..."
|
||||
sudo pacman -S --needed --noconfirm --ask 4 libfprint-git fprintd usbutils
|
||||
fi
|
||||
|
||||
omarchy-pkg-add libfprint fprintd usbutils
|
||||
|
||||
# Enroll first fingerprint
|
||||
echo -e "\e[32m\nLet's setup your right index finger as the first fingerprint.\e[0m"
|
||||
echo -e "Keep moving the finger around on sensor until the process completes.\n"
|
||||
|
||||
@@ -1,62 +1,32 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Set up the OpenSSH server, open the firewall, and authorize an SSH key
|
||||
# omarchy:args=[--key=<public-key>] [--gh-keys <github-username>]
|
||||
# omarchy:examples=omarchy-setup-security-sshd | omarchy-setup-security-sshd --gh-keys dhh | omarchy-setup-security-sshd --key="ssh-ed25519 AAAA... user@host"
|
||||
# omarchy:args=[--key=<public-key>]
|
||||
# omarchy:examples=omarchy-setup-security-sshd | omarchy-setup-security-sshd --key="ssh-ed25519 AAAA... user@host"
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
set -e
|
||||
|
||||
AUTHORIZED_KEYS="$HOME/.ssh/authorized_keys"
|
||||
KEY=""
|
||||
GITHUB_USER=""
|
||||
|
||||
# Checked while parsing, before anything is installed or opened: an empty or
|
||||
# option-shaped username otherwise falls through to the interactive menu having
|
||||
# already changed the machine, and `--gh-keys --help` would take --help as the
|
||||
# username and set the server up on its way to failing.
|
||||
require_github_user() {
|
||||
if [[ -z $1 || $1 == -* ]]; then
|
||||
echo "omarchy-setup-security-sshd: --gh-keys needs a GitHub username." >&2
|
||||
exit 2
|
||||
fi
|
||||
}
|
||||
|
||||
while (( $# > 0 )); do
|
||||
case "$1" in
|
||||
--key=*) KEY="${1#--key=}" ;;
|
||||
--gh-keys=*)
|
||||
GITHUB_USER="${1#--gh-keys=}"
|
||||
require_github_user "$GITHUB_USER"
|
||||
;;
|
||||
--gh-keys)
|
||||
shift
|
||||
GITHUB_USER="${1:-}"
|
||||
require_github_user "$GITHUB_USER"
|
||||
;;
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--key=*) KEY="${arg#--key=}" ;;
|
||||
-h | --help)
|
||||
echo "Usage: omarchy-setup-security-sshd [--key=<public-key>] [--gh-keys <github-username>]"
|
||||
echo "Usage: omarchy-setup-security-sshd [--key=<public-key>]"
|
||||
echo
|
||||
echo "Sets up the OpenSSH server, opens the SSH port in the UFW firewall,"
|
||||
echo "and authorizes an SSH key (from GitHub, pasted, or passed via --key)."
|
||||
echo
|
||||
echo "Passing --key or --gh-keys skips the prompts, so the command can run"
|
||||
echo "unattended from a script or a fresh machine's first login."
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "omarchy-setup-security-sshd: unknown option '$1'. Try --help." >&2
|
||||
echo "omarchy-setup-security-sshd: unknown option '$arg'. Try --help." >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [[ -n $KEY && -n $GITHUB_USER ]]; then
|
||||
echo "omarchy-setup-security-sshd: pass either --key or --gh-keys, not both." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
setup_sshd() {
|
||||
echo "Installing and starting the OpenSSH server..."
|
||||
omarchy-pkg-add openssh
|
||||
@@ -100,7 +70,13 @@ authorize_key() {
|
||||
}
|
||||
|
||||
authorize_keys_from_github() {
|
||||
local username="$1" keys added=0
|
||||
local username keys added=0
|
||||
|
||||
username=$(gum input --prompt "GitHub username> " --placeholder "dhh") || exit 1
|
||||
if [[ -z $username ]]; then
|
||||
echo -e "\e[31mNo GitHub username given.\e[0m" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Fetching keys from https://github.com/$username.keys..."
|
||||
if ! keys=$(curl -fsSL "https://github.com/$username.keys") || [[ -z $keys ]]; then
|
||||
@@ -119,18 +95,6 @@ authorize_keys_from_github() {
|
||||
fi
|
||||
}
|
||||
|
||||
prompt_for_github_user() {
|
||||
local username
|
||||
|
||||
username=$(gum input --prompt "GitHub username> " --placeholder "dhh") || exit 1
|
||||
if [[ -z $username ]]; then
|
||||
echo -e "\e[31mNo GitHub username given.\e[0m" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
authorize_keys_from_github "$username"
|
||||
}
|
||||
|
||||
authorize_pasted_key() {
|
||||
local key
|
||||
|
||||
@@ -143,6 +107,50 @@ authorize_pasted_key() {
|
||||
authorize_key "$key" || exit 1
|
||||
}
|
||||
|
||||
# Only called after a key is authorized. Disabling password authentication
|
||||
# before then could lock the owner out of the machine.
|
||||
disable_password_auth() {
|
||||
local config=/etc/ssh/sshd_config.d/10-omarchy-hardening.conf
|
||||
local effective_config
|
||||
|
||||
if [[ ! -s $AUTHORIZED_KEYS ]]; then
|
||||
echo -e "\e[31mCannot disable SSH password authentication without an authorized key.\e[0m" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "Disabling SSH password authentication, now that a key is authorized..."
|
||||
sudo install -Dm644 /dev/stdin "$config" <<'CONF'
|
||||
# Written by omarchy-setup-security-sshd once an SSH key was authorized.
|
||||
# Delete this file and reload sshd to allow password logins again.
|
||||
PasswordAuthentication no
|
||||
KbdInteractiveAuthentication no
|
||||
CONF
|
||||
|
||||
# Validate before reloading: a config sshd rejects would otherwise take the
|
||||
# service down on its next restart, potentially stranding a remote owner.
|
||||
if ! sudo sshd -t; then
|
||||
echo -e "\e[31msshd rejected the hardening config; removing it and leaving passwords on.\e[0m" >&2
|
||||
sudo rm -f "$config"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Syntax alone is insufficient because sshd uses the first value it reads for
|
||||
# these settings. An earlier administrator rule could leave passwords enabled.
|
||||
# Match keywords case-insensitively: OpenSSH 9.x dumps them lowercase, 10.x
|
||||
# in CamelCase.
|
||||
if ! effective_config=$(sudo sshd -T) ||
|
||||
! grep -qixF "passwordauthentication no" <<<"$effective_config" ||
|
||||
! grep -qixF "kbdinteractiveauthentication no" <<<"$effective_config"; then
|
||||
echo -e "\e[31msshd did not apply the password-authentication restrictions; removing the ineffective config.\e[0m" >&2
|
||||
sudo rm -f "$config"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Reload rather than restart so an administrator already connected keeps
|
||||
# their session.
|
||||
sudo systemctl reload sshd.service
|
||||
}
|
||||
|
||||
echo -e "\e[32mSetting up SSH server access with key-based authentication.\n\e[0m"
|
||||
|
||||
setup_sshd
|
||||
@@ -151,15 +159,16 @@ open_firewall
|
||||
echo
|
||||
if [[ -n $KEY ]]; then
|
||||
authorize_key "$KEY" || exit 1
|
||||
elif [[ -n $GITHUB_USER ]]; then
|
||||
authorize_keys_from_github "$GITHUB_USER"
|
||||
else
|
||||
case $(gum choose "Grab key from GitHub" "Paste key manually" --header "How would you like to add your SSH key?") in
|
||||
"Grab key from GitHub") prompt_for_github_user ;;
|
||||
"Grab key from GitHub") authorize_keys_from_github ;;
|
||||
"Paste key manually") authorize_pasted_key ;;
|
||||
*) exit 1 ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
disable_password_auth
|
||||
|
||||
echo -e "\e[32m\nPerfect! The SSH server is running and your key is authorized.\e[0m"
|
||||
echo "Password logins are off; this machine now accepts authorized keys only."
|
||||
echo "You can now connect with: ssh $USER@$(hostname)"
|
||||
@@ -13,6 +13,19 @@ if [[ $1 && ! $1 =~ ^[0-9]+$ ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
arm_expiry() {
|
||||
if sudo systemd-run --on-active=${MINUTES}m --timer-property=AccuracySec=1s --unit="$TIMER_NAME" \
|
||||
rm -f -- "$NOPASSWD_FILE"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "Failed to schedule passwordless sudo expiry. Revoking access now." >&2
|
||||
if ! sudo rm -f -- "$NOPASSWD_FILE"; then
|
||||
echo "CRITICAL: Could not remove $NOPASSWD_FILE. Remove it as root immediately." >&2
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
echo "Toggle passwordless sudo..."
|
||||
|
||||
# Safety: if the file exists but the timer doesn't (e.g. after reboot), clean up
|
||||
@@ -24,8 +37,7 @@ fi
|
||||
if sudo test -f "$NOPASSWD_FILE"; then
|
||||
if [[ $1 ]]; then
|
||||
sudo systemctl stop "${TIMER_NAME}.timer" 2>/dev/null
|
||||
sudo systemd-run --on-active=${MINUTES}m --timer-property=AccuracySec=1s --unit="$TIMER_NAME" \
|
||||
rm "$NOPASSWD_FILE"
|
||||
arm_expiry || exit 1
|
||||
echo "Passwordless sudo timer updated. It will now automatically disable in ${MINUTES} minutes."
|
||||
else
|
||||
sudo rm "$NOPASSWD_FILE"
|
||||
@@ -48,12 +60,11 @@ else
|
||||
if gum confirm "Enable passwordless sudo for ${MINUTES} minutes? This is a significant security risk!"; then
|
||||
echo "${USER} ALL=(ALL) NOPASSWD: ALL" | sudo tee "$NOPASSWD_FILE" > /dev/null
|
||||
sudo chmod 440 "$NOPASSWD_FILE"
|
||||
sudo systemd-run --on-active=${MINUTES}m --timer-property=AccuracySec=1s --unit="$TIMER_NAME" \
|
||||
rm "$NOPASSWD_FILE"
|
||||
arm_expiry || exit 1
|
||||
|
||||
echo ""
|
||||
echo "Passwordless sudo has been ENABLED. It will automatically disable in ${MINUTES} minutes."
|
||||
echo "Note: if you restart before then, run omarchy-sudo-passwordless again to disable it."
|
||||
echo "A restart removes the passwordless sudo rule as well."
|
||||
else
|
||||
echo "Aborted. No changes made."
|
||||
fi
|
||||
|
||||
@@ -326,6 +326,8 @@ post_theme_commands=(
|
||||
omarchy-theme-set-gnome
|
||||
omarchy-theme-set-pi
|
||||
omarchy-theme-set-claude
|
||||
omarchy-theme-set-hermes
|
||||
omarchy-theme-set-t3code
|
||||
omarchy-theme-set-browser
|
||||
omarchy-theme-set-vscode
|
||||
omarchy-theme-set-obsidian
|
||||
|
||||
Executable
+233
@@ -0,0 +1,233 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Sync the generated Omarchy theme to Hermes as a skin
|
||||
# omarchy:args=[--activate] [--wait]
|
||||
# omarchy:hidden=true
|
||||
|
||||
# A skin is Hermes' one theme unit for the desktop app, the TUI and the CLI;
|
||||
# its gateway watches the active skin file and repaints every surface on change.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
HERMES_SOURCE_PATH="$HOME/.local/state/omarchy/current/theme/hermes.yaml"
|
||||
HERMES_THEME_NAME_PATH="$HOME/.local/state/omarchy/current/theme.name"
|
||||
HERMES_HOME="${HERMES_HOME:-$HOME/.hermes}"
|
||||
HERMES_CONFIG_PATH="$HERMES_HOME/config.yaml"
|
||||
HERMES_SKIN_NAME="omarchy"
|
||||
# The command the readiness probe vets, rather than whichever hermes is on PATH.
|
||||
HERMES_COMMAND="$HOME/.local/bin/hermes"
|
||||
# Written by the desktop app once the runtime its first launch provisions is in.
|
||||
HERMES_BOOTSTRAP_MARKER="$HERMES_HOME/hermes-agent/.hermes-bootstrap-complete"
|
||||
HERMES_ACTIVATE=0
|
||||
HERMES_WAIT=0
|
||||
HERMES_WAIT_LIMIT=$((30 * 60))
|
||||
|
||||
usage() {
|
||||
echo "Usage: omarchy-theme-set-hermes [--activate] [--wait]"
|
||||
}
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--activate)
|
||||
HERMES_ACTIVATE=1
|
||||
;;
|
||||
--wait)
|
||||
HERMES_ACTIVATE=1
|
||||
HERMES_WAIT=1
|
||||
;;
|
||||
-h | --help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# A theme switch runs this beside a dozen other hooks; only --activate explains.
|
||||
note() {
|
||||
if (( HERMES_ACTIVATE == 1 )); then
|
||||
echo "$*" >&2
|
||||
fi
|
||||
}
|
||||
|
||||
# A theme applied before the template existed has no skin rendered yet.
|
||||
if [[ ! -f $HERMES_SOURCE_PATH ]]; then
|
||||
(( HERMES_ACTIVATE == 1 )) || exit 0
|
||||
|
||||
if [[ ! -s $HERMES_THEME_NAME_PATH ]]; then
|
||||
echo "Hermes skin source missing: $HERMES_SOURCE_PATH" >&2
|
||||
echo "Select an Omarchy theme first." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
omarchy-theme-refresh
|
||||
|
||||
if [[ ! -f $HERMES_SOURCE_PATH ]]; then
|
||||
echo "Hermes skin source missing after refreshing the theme: $HERMES_SOURCE_PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# The first launch takes minutes and may be abandoned; the readiness probe
|
||||
# would start Hermes to answer, so poll for the marker instead.
|
||||
if (( HERMES_WAIT == 1 )); then
|
||||
waited=0
|
||||
until [[ -f $HERMES_BOOTSTRAP_MARKER && -f $HERMES_CONFIG_PATH ]]; do
|
||||
if (( waited >= HERMES_WAIT_LIMIT )); then
|
||||
echo "Hermes did not finish setting up within $((HERMES_WAIT_LIMIT / 60)) minutes; run omarchy-theme-set-hermes --activate once it has." >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
sleep 10
|
||||
waited=$((waited + 10))
|
||||
done
|
||||
fi
|
||||
|
||||
# Provisioning creates ~/.hermes on every machine for the Omarchy skill; the
|
||||
# config is what Hermes writes once it has actually run.
|
||||
if [[ ! -f $HERMES_CONFIG_PATH ]]; then
|
||||
note "Hermes is not set up yet; launch it once, then run omarchy-theme-set-hermes --activate."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Hermes parses the skin as YAML and hands its strings to every surface, so only
|
||||
# the name, a plain description and #rrggbb colours may reach it, in the order
|
||||
# YAML needs them. YAML breaks lines on bytes grep does not, so the bytes are
|
||||
# counted first, NUL included.
|
||||
skin_is_well_formed() {
|
||||
local skin="$1"
|
||||
|
||||
[[ -f $skin ]] &&
|
||||
(( $(LC_ALL=C tr -d ' -~\n' <"$skin" | wc -c) == 0 )) &&
|
||||
awk -v name="name: $HERMES_SKIN_NAME" '
|
||||
bad { next }
|
||||
/^#/ || /^[[:space:]]*$/ { next }
|
||||
!seen_name { if ($0 == name) seen_name = 1; else bad = 1; next }
|
||||
!seen_colors {
|
||||
if ($0 == "colors:") seen_colors = 1
|
||||
else if (!seen_description && $0 ~ /^description: [A-Za-z0-9 ,.()-]{0,200}$/) seen_description = 1
|
||||
else bad = 1
|
||||
next
|
||||
}
|
||||
/^ [a-z_]{1,64}: "#[0-9a-fA-F]{6}"$/ { colors++; next }
|
||||
{ bad = 1 }
|
||||
END { exit (bad || !seen_colors || colors == 0) }
|
||||
' "$skin"
|
||||
}
|
||||
|
||||
# The check has to cover the bytes that get published, and the theme can change
|
||||
# underneath between the two, so a private copy is taken and that is checked.
|
||||
snapshot_dir=$(mktemp -d)
|
||||
trap 'rm -rf "$snapshot_dir"' EXIT
|
||||
HERMES_SNAPSHOT="$snapshot_dir/$HERMES_SKIN_NAME.yaml"
|
||||
|
||||
take_snapshot() {
|
||||
cp "$HERMES_SOURCE_PATH" "$HERMES_SNAPSHOT" 2>/dev/null && skin_is_well_formed "$HERMES_SNAPSHOT"
|
||||
}
|
||||
|
||||
if ! take_snapshot; then
|
||||
echo "Skipping Hermes skin: $(basename "$HERMES_SOURCE_PATH") is not a plain color palette." >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# The gateway reads the file whole on an mtime change, so the write is atomic;
|
||||
# -T so a directory at the skin's path is an error rather than a destination.
|
||||
publish_skin() {
|
||||
local skins_dir="$1"
|
||||
local tmp
|
||||
|
||||
mkdir -p "$skins_dir" 2>/dev/null || return 1
|
||||
tmp=$(mktemp "$skins_dir/$HERMES_SKIN_NAME.yaml.XXXXXX" 2>/dev/null) || return 1
|
||||
if ! cp "$HERMES_SNAPSHOT" "$tmp" 2>/dev/null || ! mv -T "$tmp" "$skins_dir/$HERMES_SKIN_NAME.yaml" 2>/dev/null; then
|
||||
rm -f "$tmp"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# A profile is a Hermes home of its own; existing ones get the skin, none are
|
||||
# made, and one that cannot take it does not cost the others.
|
||||
publish_skin_everywhere() {
|
||||
local profile
|
||||
|
||||
publish_skin "$HERMES_HOME/skins" || {
|
||||
echo "Could not publish the Hermes skin to $HERMES_HOME/skins." >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
for profile in "$HERMES_HOME"/profiles/*/; do
|
||||
[[ -d $profile ]] || continue
|
||||
publish_skin "${profile%/}/skins" || note "Could not publish the skin to the Hermes profile $(basename "$profile")."
|
||||
done
|
||||
}
|
||||
|
||||
publish_skin_everywhere
|
||||
|
||||
# Hermes reads the config of the profile named in active_profile; the profile
|
||||
# exists once its directory does, with or without a config of its own.
|
||||
active_config_path() {
|
||||
local profile
|
||||
|
||||
profile=$(cat "$HERMES_HOME/active_profile" 2>/dev/null || true)
|
||||
profile=${profile,,}
|
||||
|
||||
if [[ -n $profile && $profile != "default" && -d $HERMES_HOME/profiles/$profile ]]; then
|
||||
echo "$HERMES_HOME/profiles/$profile/config.yaml"
|
||||
else
|
||||
echo "$HERMES_CONFIG_PATH"
|
||||
fi
|
||||
}
|
||||
|
||||
# A theme switch finishes the hand-over only for the app Omarchy installed, and
|
||||
# only Hermes' default is ever replaced, so a config plainly naming another skin
|
||||
# ends it here without starting Hermes. Anything less plain is for Hermes to read.
|
||||
if (( HERMES_ACTIVATE == 0 )); then
|
||||
omarchy-pkg-present hermes-desktop || exit 0
|
||||
|
||||
skin_line=$(grep -m1 -x ' skin: .*' "$(active_config_path)" 2>/dev/null || true)
|
||||
case "${skin_line# skin: }" in
|
||||
"" | default | null | true | false | *[!A-Za-z0-9_-]*) ;;
|
||||
*) exit 0 ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Omarchy's cold stub installs Hermes when run, so ask the probe before running it.
|
||||
if ! omarchy-install-hermes-cli --check 2>/dev/null; then
|
||||
note "Hermes is not ready, so the Omarchy skin is published but not active."
|
||||
note "Once Hermes runs, activate it with: hermes config set display.skin $HERMES_SKIN_NAME"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Only Hermes' own default is replaced, so a skin chosen in Hermes stays; an
|
||||
# answer that did not come is not a default.
|
||||
if ! current_skin=$(timeout 15 "$HERMES_COMMAND" config get display.skin 2>/dev/null); then
|
||||
note "Hermes did not say which skin it is on, so the Omarchy skin is published but not active."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ -n $current_skin && $current_skin != "default" && $current_skin != "$HERMES_SKIN_NAME" ]]; then
|
||||
note "Hermes is set to the '$current_skin' skin; leaving it. Switch with: hermes config set display.skin $HERMES_SKIN_NAME"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Hermes' own writer: it updates the active profile's config and touches the
|
||||
# skin file so a running gateway broadcasts the change. A refusal is cosmetic.
|
||||
if ! timeout 30 "$HERMES_COMMAND" config set display.skin "$HERMES_SKIN_NAME" >/dev/null 2>&1; then
|
||||
note "Hermes refused to switch skins, so the Omarchy skin is published but not active."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
note "Hermes is on the Omarchy skin."
|
||||
|
||||
# The desktop applies a skin only from a broadcast, and a config written before
|
||||
# the gateway seeded its watcher goes unannounced; a later write is announced.
|
||||
# The theme may have changed underneath in the meantime, so it is checked again.
|
||||
if (( HERMES_WAIT == 1 )); then
|
||||
sleep 60
|
||||
|
||||
if take_snapshot; then
|
||||
publish_skin_everywhere
|
||||
fi
|
||||
fi
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Sync the generated Omarchy theme to T3 Code
|
||||
# omarchy:hidden=true
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
T3CODE_SOURCE_PATH="$HOME/.local/state/omarchy/current/theme/t3code.json"
|
||||
T3CODE_HOME="${T3CODE_HOME:-$HOME/.t3}"
|
||||
T3CODE_STATE_DIR="$T3CODE_HOME/userdata"
|
||||
# The filename is the theme id T3 Code shows and `t3 theme set` accepts, and
|
||||
# it stays stable while the colors change underneath on every theme switch.
|
||||
T3CODE_THEME_PATH="$T3CODE_STATE_DIR/themes/omarchy.json"
|
||||
|
||||
[[ -f $T3CODE_SOURCE_PATH ]] || exit 0
|
||||
|
||||
# A theme without an accent or a background leaves its placeholder unresolved.
|
||||
# Publishing that would only make T3 Code reject the file, so skip instead and
|
||||
# leave the previous palette in place.
|
||||
if grep -q '{{' "$T3CODE_SOURCE_PATH"; then
|
||||
echo "Skipping T3 Code theme: $(basename "$T3CODE_SOURCE_PATH") has unresolved colors." >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Only follow an install that already exists. Creating the directory here would
|
||||
# leave a stray T3 Code state dir on machines that do not run it.
|
||||
[[ -d $T3CODE_STATE_DIR ]] || exit 0
|
||||
|
||||
# T3 Code watches this file and retints every connected client on change, so
|
||||
# the write has to be atomic: a half-written file would read as invalid.
|
||||
mkdir -p "$(dirname "$T3CODE_THEME_PATH")"
|
||||
tmp=$(mktemp "$T3CODE_THEME_PATH.XXXXXX")
|
||||
cp "$T3CODE_SOURCE_PATH" "$tmp"
|
||||
mv "$tmp" "$T3CODE_THEME_PATH"
|
||||
@@ -5,3 +5,9 @@
|
||||
# omarchy:examples=omarchy toggle bar | omarchy toggle bar off | omarchy toggle bar on
|
||||
|
||||
omarchy-toggle bar-off "${1:-toggle}"
|
||||
|
||||
# The shell's watch on the toggles directory can miss flag changes that land in
|
||||
# quick succession, stranding the bar off screen until the shell restarts.
|
||||
# Nudge the bar to re-read the flag; quiet best-effort so the toggle still
|
||||
# works when the shell is not up.
|
||||
omarchy-shell -q omarchy.bar syncHidden
|
||||
@@ -3,6 +3,35 @@
|
||||
# omarchy:summary=Toggle dedicated vs integrated GPU mode via supergfxd (for hybrid gpu laptops, like Asus G14).
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
install_root_file() {
|
||||
local source="$1"
|
||||
local destination="$2"
|
||||
local mode="$3"
|
||||
local stage
|
||||
|
||||
stage=$(sudo /usr/bin/mktemp -- "${destination%/*}/.${destination##*/}.omarchy.XXXXXX") || return 1
|
||||
safe_stage_path "$stage" "$destination" || return 1
|
||||
|
||||
if sudo /usr/bin/install -m "$mode" -o root -g root -T "$source" "$stage" &&
|
||||
sudo /usr/bin/mv -Tf -- "$stage" "$destination"; then
|
||||
return 0
|
||||
else
|
||||
safe_stage_path "$stage" "$destination" && sudo /usr/bin/rm -f -- "$stage"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
safe_stage_path() {
|
||||
local stage="$1"
|
||||
local destination="$2"
|
||||
local prefix suffix
|
||||
|
||||
prefix="${destination%/*}/.${destination##*/}.omarchy."
|
||||
[[ $stage == "$prefix"* ]] || return 1
|
||||
suffix=${stage#"$prefix"}
|
||||
[[ $suffix =~ ^[[:alnum:]]{6}$ ]]
|
||||
}
|
||||
|
||||
if omarchy-cmd-missing supergfxctl; then
|
||||
omarchy-pkg-add supergfxctl
|
||||
|
||||
@@ -54,18 +83,31 @@ case "$gpu_mode" in
|
||||
;;
|
||||
"Hybrid")
|
||||
if gum confirm "Use only integrated GPU and reboot?"; then
|
||||
# Switch to integrated mode and ensure vfio is enabled (needed for sleep/wake trick)
|
||||
sudo sed -i "s/\"mode\": \".*\"/\"mode\": \"Integrated\"/" /etc/supergfxd.conf
|
||||
sudo sed -i 's/"vfio_enable": false/"vfio_enable": true/' /etc/supergfxd.conf
|
||||
|
||||
# Force igpu mode after system sleep (or dgpu could get activated)
|
||||
sudo mkdir -p /usr/lib/systemd/system-sleep
|
||||
sudo cp -p "$OMARCHY_PATH/default/systemd/system-sleep/force-igpu" /usr/lib/systemd/system-sleep/
|
||||
|
||||
# Delay supergfxd startup to avoid race condition with display manager
|
||||
# that can cause system freeze when booting in Integrated mode
|
||||
sudo mkdir -p /etc/systemd/system/supergfxd.service.d
|
||||
sudo cp -p "$OMARCHY_PATH/default/systemd/system/supergfxd.service.d/delay-start.conf" /etc/systemd/system/supergfxd.service.d/
|
||||
if ! install_root_file "$OMARCHY_PATH/default/systemd/system/supergfxd.service.d/delay-start.conf" \
|
||||
/etc/systemd/system/supergfxd.service.d/delay-start.conf 0644; then
|
||||
echo "Could not install the supergfxd startup-delay override" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Publish the self-guarding sleep hook before enabling Integrated mode. It
|
||||
# remains inert while the config says Hybrid, so any failed step is safe to
|
||||
# retry without leaving the GPU config partially switched.
|
||||
sudo mkdir -p /usr/lib/systemd/system-sleep
|
||||
if ! install_root_file "$OMARCHY_PATH/default/systemd/system-sleep/force-igpu" \
|
||||
/usr/lib/systemd/system-sleep/force-igpu 0755; then
|
||||
echo "Could not install the force-igpu system-sleep hook" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Switch both settings in one atomic config rewrite only after every
|
||||
# supporting file has been installed successfully.
|
||||
sudo sed -i \
|
||||
-e 's/"mode": ".*"/"mode": "Integrated"/' \
|
||||
-e 's/"vfio_enable": false/"vfio_enable": true/' \
|
||||
/etc/supergfxd.conf
|
||||
|
||||
omarchy-system-reboot
|
||||
fi
|
||||
|
||||
@@ -101,6 +101,11 @@ if [[ ! -f $image_path ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if omarchy-cmd-missing magick; then
|
||||
echo "ImageMagick is required to convert logo images" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ $mode == "braille" ]]; then
|
||||
pixel_width=$((width * 2))
|
||||
pixel_height=$((height * 4))
|
||||
|
||||
@@ -175,7 +175,10 @@ target_home=$(getent passwd "$target_user" | cut -d: -f6)
|
||||
[[ -n $target_home && -d $target_home ]] || fail "Home directory for '$target_user' was not found."
|
||||
target_uid=$(id -u "$target_user")
|
||||
target_runtime_dir="/run/user/$target_uid"
|
||||
package_path="/usr/share/omarchy/bin:/usr/local/bin:/usr/bin:/bin:$target_home/.local/bin"
|
||||
# User-local commands are needed only after dropping to the target user. Never
|
||||
# expose their search path to commands run through as_root.
|
||||
root_path=/usr/share/omarchy/bin:/usr/local/bin:/usr/bin:/bin
|
||||
package_path="$root_path:$target_home/.local/bin"
|
||||
|
||||
as_root() {
|
||||
if (( EUID == 0 )); then
|
||||
@@ -661,7 +664,7 @@ configure_lock_authentication() {
|
||||
as_root env \
|
||||
OMARCHY_INSTALL_USER="$target_user" \
|
||||
OMARCHY_PATH=/usr/share/omarchy \
|
||||
PATH="$package_path" \
|
||||
PATH="$root_path" \
|
||||
"$apply_lock"
|
||||
}
|
||||
|
||||
@@ -1287,7 +1290,7 @@ apply_firewall_defaults() {
|
||||
fi
|
||||
|
||||
log "Applying Omarchy firewall defaults"
|
||||
as_root env OMARCHY_PATH=/usr/share/omarchy PATH="$package_path" \
|
||||
as_root env OMARCHY_PATH=/usr/share/omarchy PATH="$root_path" \
|
||||
bash -euo pipefail "$firewall_script" ||
|
||||
warn "Could not apply firewall defaults; run 'sudo bash $firewall_script' after reboot."
|
||||
}
|
||||
@@ -1566,7 +1569,6 @@ refresh xournalpp/settings.xml 1c1a9efbf1b6dc7813bf3440fd5bf8409fc283e8d0192ca29
|
||||
# fastfetch/config.jsonc -> /etc/fastfetch/config.jsonc
|
||||
# fontconfig/fonts.conf -> /usr/share/fontconfig/conf.avail/50-omarchy.conf
|
||||
# mimeapps.list -> /usr/share/applications/mimeapps.list
|
||||
# omarchy.ttf -> /usr/share/fonts/omarchy/omarchy.ttf
|
||||
# systemd/user/bt-agent.service -> /usr/lib/systemd/user/bt-agent.service
|
||||
# systemd/user/omarchy-recover-internal-monitor.service -> /usr/lib/systemd/user/omarchy-recover-internal-monitor.service
|
||||
# systemd/user/omarchy-sleep-lock.service -> /usr/lib/systemd/user/omarchy-sleep-lock.service
|
||||
@@ -1599,7 +1601,6 @@ retire fontconfig/fonts.conf 6dec98b539388b95ecfdb3c7ab951001c712820eb0581002832
|
||||
retire fontconfig/fonts.conf 3545f6c5a8c1465df7a4e251b3c2047d4ba03654c86636794f64cb0d8ea8ef63
|
||||
retire fontconfig/fonts.conf 0f085b449f1cbe8eda3b59a6235bf2a3ddb6e982ab5d22fa642248916e63bafc
|
||||
retire mimeapps.list 3b574cef135b5deb7a8a0c7e17139037cf1fe155300e3809f60bed0e04120975
|
||||
retire omarchy.ttf e55e67119e82f56f92d90cbf54b7ccc1b2946b32c535a29370439d7ef5215966
|
||||
retire systemd/user/bt-agent.service 0406b577a1225dc2a9f86638d3c346eb3635168576f04050be50ebcc0be6be12
|
||||
retire systemd/user/omarchy-recover-internal-monitor.service b9b92cedc44cf3cb6216948629be55b53d16746e31896dc6469fd49ba55e82f4
|
||||
retire systemd/user/omarchy-recover-internal-monitor.service e1483079b9f2aefcd43b4722c75a31643e5f3bb5a2eada5f52f1d7d201e8c289
|
||||
@@ -2375,6 +2376,8 @@ cleanup_retired_services
|
||||
ensure_sleep_lock_service
|
||||
remove_retired_default_packages
|
||||
run_final_system_package_upgrade
|
||||
# This also retires the stock ~/.local/share/fonts/omarchy.ttf missed by the
|
||||
# former ~/.config retirement entry, through migration 1788848726.
|
||||
run_post_upgrade_migrations
|
||||
run_post_upgrade_update_steps
|
||||
refresh_current_theme_after_upgrade
|
||||
|
||||
@@ -14,6 +14,8 @@ fi
|
||||
|
||||
if grep -q "https://pkgs.omarchy.org/stable/" /etc/pacman.conf; then
|
||||
pkgs="stable"
|
||||
elif grep -q "https://pkgs.omarchy.org/rc/" /etc/pacman.conf; then
|
||||
pkgs="rc"
|
||||
elif grep -q "https://pkgs.omarchy.org/edge/" /etc/pacman.conf; then
|
||||
pkgs="edge"
|
||||
else
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Print the OEM Windows product key stored in firmware
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
MSDM=${OMARCHY_MSDM_PATH:-/sys/firmware/acpi/tables/MSDM}
|
||||
|
||||
if [[ ! -e $MSDM ]]; then
|
||||
echo "No Windows license key found in firmware." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
read_msdm() {
|
||||
if [[ -r $MSDM ]]; then
|
||||
"$@" "$MSDM"
|
||||
else
|
||||
sudo "$@" "$MSDM"
|
||||
fi
|
||||
}
|
||||
|
||||
extract_key() {
|
||||
grep -aoEm1 '[A-Z0-9]{5}(-[A-Z0-9]{5}){4}'
|
||||
}
|
||||
|
||||
key=""
|
||||
if omarchy-cmd-present strings; then
|
||||
key=$(read_msdm strings | extract_key) || true
|
||||
fi
|
||||
|
||||
if [[ -z $key ]]; then
|
||||
key=$(read_msdm cat | extract_key) || true
|
||||
fi
|
||||
|
||||
if [[ -z $key ]]; then
|
||||
echo "Firmware license table found, but no Windows product key could be extracted." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf '%s\n' "$key"
|
||||
@@ -1,10 +1,10 @@
|
||||
-- See https://wiki.hypr.land/Configuring/Basics/Monitors/
|
||||
-- List current monitors and supported resolutions with: hyprctl monitors all
|
||||
|
||||
-- Monitor scale is Hyprland's scale for the output. It sizes everything
|
||||
-- Wayland-native, accepts fractions (1.6, 1.75), and applies immediately.
|
||||
-- "auto" lets Hyprland pick per display.
|
||||
local omarchy_gdk_scale = 2
|
||||
local omarchy_monitor_scale = "auto"
|
||||
|
||||
hl.env("GDK_SCALE", tostring(omarchy_gdk_scale))
|
||||
hl.monitor({ output = "", mode = "preferred", position = "auto", scale = omarchy_monitor_scale })
|
||||
|
||||
-- Configure a specific monitor.
|
||||
@@ -12,11 +12,3 @@ hl.monitor({ output = "", mode = "preferred", position = "auto", scale = omarchy
|
||||
|
||||
-- Portrait/rotated secondary monitor (transform: 1 = 90°, 3 = 270°).
|
||||
-- hl.monitor({ output = "DP-2", mode = "preferred", position = "auto", scale = 1, transform = 1 })
|
||||
|
||||
-- GDK scale is GDK_SCALE, the factor GTK draws its own UI at. It's what
|
||||
-- sizes X11/XWayland windows, which Omarchy leaves unscaled so they stay
|
||||
-- crisp instead of being stretched by the compositor. GTK only honors whole
|
||||
-- numbers, so use the nearest integer to the monitor scale, and restart an
|
||||
-- app for a change to reach it.
|
||||
local omarchy_gdk_scale = 2
|
||||
hl.env("GDK_SCALE", tostring(omarchy_gdk_scale))
|
||||
+14
-28
@@ -1,35 +1,21 @@
|
||||
# Remove the include below to disconnect Kitty from Omarchy's theming system.
|
||||
include ~/.local/state/omarchy/current/theme/kitty.conf
|
||||
|
||||
# Settings below override Omarchy's defaults in /etc/xdg/kitty/kitty.conf.
|
||||
# Learn more: https://sw.kovidgoyal.net/kitty/conf/
|
||||
|
||||
# Font
|
||||
font_family JetBrainsMono Nerd Font
|
||||
bold_italic_font auto
|
||||
font_size 9.0
|
||||
# font_family JetBrainsMono Nerd Font
|
||||
# font_size 12
|
||||
|
||||
# Window
|
||||
window_padding_width 14
|
||||
hide_window_decorations yes
|
||||
confirm_os_window_close 0
|
||||
# Window padding
|
||||
# window_padding_width 14
|
||||
|
||||
# Keybindings
|
||||
map ctrl+insert copy_to_clipboard
|
||||
map shift+insert paste_from_clipboard
|
||||
# Send Shift+Enter as CSI-u so TUIs can distinguish it from Enter.
|
||||
map shift+enter send_text all \e[13;2u
|
||||
# Kitty legacy encoding sends Alt+Shift+Enter the same as Alt+Enter; send CSI-u so tmux can match M-S-Enter.
|
||||
map alt+shift+enter send_text all \e[13;4u
|
||||
# Unmap a shortcut, passing it through to the terminal application
|
||||
# map ctrl+insert
|
||||
|
||||
# Allow remote access
|
||||
allow_remote_control yes
|
||||
listen_on unix:${XDG_RUNTIME_DIR}/omarchy-kitty-{kitty_pid}
|
||||
# Set or replace a shortcut
|
||||
# map ctrl+shift+c copy_to_clipboard
|
||||
|
||||
# Aesthetics
|
||||
cursor_shape block
|
||||
cursor_blink_interval 0
|
||||
shell_integration no-cursor
|
||||
enable_audio_bell no
|
||||
|
||||
# Minimal Tab bar styling
|
||||
tab_bar_edge bottom
|
||||
tab_bar_style powerline
|
||||
tab_powerline_style slanted
|
||||
tab_title_template {title}{' :{}:'.format(num_windows) if num_windows > 1 else ''}
|
||||
# Remove all inherited shortcuts, including Kitty's built-in shortcuts
|
||||
# clear_all_shortcuts yes
|
||||
@@ -10,7 +10,7 @@
|
||||
// label Visible row title.
|
||||
// action Shell command to run. If omitted, the row is a submenu.
|
||||
// target Existing submenu id to open. Use for links/aliases.
|
||||
// provider Name of a shell-defined runtime row source (e.g. "fonts").
|
||||
// provider Runtime provider function/command returning JSON rows.
|
||||
// aliases alternate `omarchy menu summon <name>` routes; also searchable.
|
||||
// description Optional subtitle and extra search text.
|
||||
// when Shell condition; hide row when it fails.
|
||||
@@ -21,8 +21,8 @@
|
||||
// "personal.notes": {"icon":"","label":"Notes","action":"omarchy-launch-editor ~/notes"},
|
||||
// "personal.files": {"icon":"","label":"Files","action":"uwsm-app -- nautilus ~/Documents"},
|
||||
//
|
||||
// provider can only reference row sources the shell already defines (the
|
||||
// providers map in Menu.qml). Static submenus only need dotted ids.
|
||||
// Only use provider when a provider_name function or command named "name"
|
||||
// returns JSON rows. Static submenus only need dotted ids.
|
||||
//
|
||||
// Example: replace the default About action by reusing the same id. Existing
|
||||
// fields are kept unless overridden.
|
||||
|
||||
@@ -87,38 +87,7 @@ ambiguous, say so rather than assembling confidence out of guesswork.
|
||||
|
||||
**Leave the system as you found it.** Diagnosis reads; it does not fix, tidy, or
|
||||
reconfigure. The one thing to clean up is your own: delete the core you extracted
|
||||
above, which is a copy of the crashed process's memory. The single change a
|
||||
diagnosis may make is the mute below, and only when the user asks for it.
|
||||
|
||||
## Offer to stop the notifications for this program
|
||||
|
||||
A crash you have explained often keeps happening anyway. Finish by offering to
|
||||
silence notifications for **that one program**, and never run it unprompted. Say
|
||||
how to lift it in the same breath, so it is not a one-way door.
|
||||
|
||||
```bash
|
||||
omarchy-crash-mute '<program>' # silence it
|
||||
omarchy-crash-mute '<program>' off # let it speak again
|
||||
omarchy-crash-mute # list what is muted
|
||||
```
|
||||
|
||||
Pass the `binary:` path from the crash facts, or the `process:` name where no
|
||||
binary was recorded; the command reduces either to the name the watcher keys on.
|
||||
A diagnosis run by hand from `omarchy agent crash <pid>` has neither, so take
|
||||
them from `coredumpctl info`. Prefer the binary: a process name is truncated to
|
||||
15 characters and a basename is not, so muting the truncated form matches
|
||||
nothing, forever, while looking like it worked.
|
||||
|
||||
Quote it. The name is whatever the crashed program's author called a file, and a
|
||||
single quote inside one closes yours and runs the rest as your shell.
|
||||
|
||||
The key is a bare name, so anything run through an interpreter is keyed as the
|
||||
interpreter: muting `python3.13` silences every Python program on the machine.
|
||||
Say so rather than quietly doing it.
|
||||
|
||||
None of this fixes anything, and a mute offered in place of a fix that was within
|
||||
reach is the wrong answer. For every program rather than one, the switch is
|
||||
_Trigger > Toggle > Crash Capture_.
|
||||
above, which is a copy of the crashed process's memory.
|
||||
|
||||
## If it is an Omarchy bug
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ validated_on="2026-07-24"
|
||||
validated_hardware="XPS 14 DA14260 (0DB9)"
|
||||
|
||||
## Measured against that reference under a dense pink-weighted multitone of 104
|
||||
## bin-aligned tones. See docs/audio-tuning.md for how to reproduce these.
|
||||
## bin-aligned tones. See docs/AUDIO-TUNING.md for how to reproduce these.
|
||||
magnitude_rms_db="1.24"
|
||||
bass_group_delay_swing_ms="13.2"
|
||||
limiter_headroom_db="1.6" ## worst-case peak on a hot master vs -1 dBFS
|
||||
|
||||
@@ -11,9 +11,11 @@ The private-use glyphs in `omarchy.ttf` are:
|
||||
- `U+E906` — LM Studio, from <https://simpleicons.org/icons/lmstudio.svg>
|
||||
- `U+E907` — Ollama, from <https://simpleicons.org/icons/ollama.svg>
|
||||
- `U+E908` — T3 Code, traced from the app icon in <https://aur.archlinux.org/cgit/aur.git/plain/t3code-icon.png?h=t3code-bin>, since upstream publishes no monochrome SVG
|
||||
- `U+E909` — Ori, from <https://openrouter.ai/brand/v2/openrouter-glyph-dark.svg>, OpenRouter's own mark: Ori ships no separate logo and its product page uses this one
|
||||
- `U+E90A` — Dim Agent, traced from the app icon at <https://dimcode.dev/apple-touch-icon.png>, since upstream publishes no monochrome SVG
|
||||
- `U+E90B` — Kimi Code, from <https://simpleicons.org/icons/kimi.svg>
|
||||
|
||||
- `U+E90A` — Hermes, Font Awesome's staff-snake (CC BY 4.0) from <https://fontawesome.com/icons/staff-snake>, the mark Hermes serves as its favicon: their app icon is a portrait that reads as a smudge at menu size
|
||||
- `U+E90B` — Perplexity, from <https://simpleicons.org/icons/perplexity.svg>
|
||||
- `U+E90C` — OpenClaw, traced from the lobster mascot the openclaw package ships as `dist/control-ui/favicon.svg`, since upstream publishes no monochrome SVG
|
||||
- `U+E90D` — Cursor, from <https://simpleicons.org/icons/cursor.svg>
|
||||
|
||||
The agent marks are monochrome so the menu can render them using the active
|
||||
theme's foreground and selection colors.
|
||||
Binary file not shown.
@@ -3,12 +3,9 @@
|
||||
o.window(".*[Rr]esolve.*", {
|
||||
float = true,
|
||||
stay_focused = true,
|
||||
-- Prevent modal dialog pointer warps when focus follows the mouse.
|
||||
no_follow_mouse = true,
|
||||
tag = "-default-opacity",
|
||||
opacity = "1 1",
|
||||
})
|
||||
|
||||
o.window({ class = ".*[Rr]esolve.*", title = "^DaVinci Resolve( Studio)? - .+$" }, { fullscreen = true })
|
||||
-- Resolve exposes the Voiceover panel under the generic "Dialog" title.
|
||||
o.window({ class = ".*[Rr]esolve.*", title = "^(DaVinci Resolve( Studio)? - .+|Project Manager|Preferences|Find Directory|Dialog)$" }, { stay_focused = false })
|
||||
o.window({ class = ".*[Rr]esolve.*", title = "^(DaVinci Resolve( Studio)? - .+|Project Manager)$" }, { stay_focused = false })
|
||||
@@ -0,0 +1,7 @@
|
||||
-- Hermes Desktop's frameless HUD manages its own geometry.
|
||||
o.window({ class = "^Hermes$", title = "^Hermes HUD$" }, {
|
||||
tag = "-default-opacity",
|
||||
float = true,
|
||||
border_size = 0,
|
||||
opacity = "1 1",
|
||||
})
|
||||
@@ -1,5 +0,0 @@
|
||||
-- Keep the Windows VM display opaque instead of applying the default window opacity.
|
||||
o.window({ class = "^xfreerdp$", title = "^Windows VM - Omarchy$" }, {
|
||||
tag = "-default-opacity",
|
||||
opacity = "1 1",
|
||||
})
|
||||
@@ -1,5 +1,4 @@
|
||||
o.bind("SUPER + W", "Close window", hl.dsp.window.close())
|
||||
o.bind("SUPER + Q", "Close window", hl.dsp.window.close())
|
||||
o.bind("CTRL + ALT + DELETE", "Close all windows", "omarchy-hyprland-window-close-all")
|
||||
|
||||
o.bind("SUPER + J", "Toggle window split", hl.dsp.layout("togglesplit"))
|
||||
@@ -27,8 +26,6 @@ end
|
||||
|
||||
o.bind("SUPER + S", "Toggle scratchpad", hl.dsp.workspace.toggle_special("scratchpad"))
|
||||
o.bind("SUPER + ALT + S", "Move window to scratchpad", hl.dsp.window.move({ workspace = "special:scratchpad", follow = false }))
|
||||
o.bind("SUPER + grave", "Toggle scratchpad", hl.dsp.workspace.toggle_special("scratchpad"))
|
||||
o.bind("SUPER + SHIFT + grave", "Move window to scratchpad", hl.dsp.window.move({ workspace = "special:scratchpad", follow = false }))
|
||||
|
||||
o.bind("SUPER + TAB", "Next workspace", hl.dsp.focus({ workspace = "e+1" }))
|
||||
o.bind("SUPER + SHIFT + TAB", "Previous workspace", hl.dsp.focus({ workspace = "e-1" }))
|
||||
|
||||
@@ -86,6 +86,7 @@ hl.animation({ leaf = "layersOut", enabled = true, speed = 1.5, bezier = "linear
|
||||
hl.animation({ leaf = "fadeLayersIn", enabled = true, speed = 1.79, bezier = "almostLinear" })
|
||||
hl.animation({ leaf = "fadeLayersOut", enabled = true, speed = 1.39, bezier = "almostLinear" })
|
||||
hl.animation({ leaf = "workspaces", enabled = false })
|
||||
hl.animation({ leaf = "specialWorkspace", enabled = true, speed = 3, bezier = "easeOutQuint", style = "slidevert" })
|
||||
|
||||
hl.config({
|
||||
dwindle = {
|
||||
|
||||
@@ -15,7 +15,6 @@ if _G.omarchy_default_bindings ~= false then
|
||||
end
|
||||
require("default.hypr.envs")
|
||||
require("default.hypr.looknfeel")
|
||||
require("default.hypr.qconsole")
|
||||
require("default.hypr.input")
|
||||
require("default.hypr.windows")
|
||||
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
-- The scratchpad, presented as a Quake console: a dimmed overlay that drops
|
||||
-- down over whatever workspace you are on. Its bindings live in
|
||||
-- bindings/tiling.lua, and its slide is animated below.
|
||||
|
||||
-- How much of the usable screen the console covers, measured from the top.
|
||||
local share = 0.5
|
||||
|
||||
-- Seed the console with the default agent the first time it opens, rather than
|
||||
-- at boot, so nothing is running until it is wanted. The exec rule has to pin
|
||||
-- the workspace itself: Hyprland only tags a spawn with the workspace it came
|
||||
-- from while misc.initial_workspace_tracking is on, and looknfeel turns it off.
|
||||
-- Omarchy ships without a default agent, and omarchy-agent exits without
|
||||
-- opening anything when none is set, so until one is picked this just opens an
|
||||
-- empty console.
|
||||
local seed = "[workspace special:scratchpad silent] omarchy-agent"
|
||||
|
||||
-- Dimming only applies while a special workspace is open, so the console gets
|
||||
-- its separation from the workspace underneath without costing anything the
|
||||
-- rest of the time.
|
||||
hl.config({
|
||||
decoration = {
|
||||
dim_special = 0.6,
|
||||
},
|
||||
})
|
||||
|
||||
-- Refitting replaces the rule in place rather than stacking a new one, but it
|
||||
-- still schedules a monitor and window state refresh, and monitor.focused fires
|
||||
-- on every hop between screens. Most of those hops do not change the number, so
|
||||
-- only write the rule when it actually moves.
|
||||
local covering = nil
|
||||
|
||||
local function cover(bottom)
|
||||
if covering == bottom then
|
||||
return
|
||||
end
|
||||
covering = bottom
|
||||
|
||||
hl.workspace_rule({
|
||||
workspace = "special:scratchpad",
|
||||
gaps_in = 0,
|
||||
gaps_out = { top = 0, right = 0, bottom = bottom, left = 0 },
|
||||
|
||||
-- Nothing to highlight in a console that is only ever focused when it is
|
||||
-- open, and the active border reads as a stray frame around a panel that
|
||||
-- is already set apart by the dimming behind it.
|
||||
no_border = true,
|
||||
|
||||
on_created_empty = seed,
|
||||
})
|
||||
end
|
||||
|
||||
-- Sizing the console with a window rule would freeze it at whatever the screen
|
||||
-- measured when it first opened, because Hyprland resolves those expressions
|
||||
-- once, as the window maps. Rescaling the monitor afterwards would leave a
|
||||
-- console that is no longer half of anything. Gaps are re-applied by the layout
|
||||
-- instead, so the console is sized by the gap left underneath it and that gap
|
||||
-- is recomputed whenever the monitor layout changes.
|
||||
local function fit()
|
||||
local monitor = hl.get_active_monitor()
|
||||
|
||||
-- A monitor handle whose output has gone away answers nil to every field, and
|
||||
-- layout changes are exactly when that happens, so this also covers reading
|
||||
-- height and reserved below.
|
||||
if not monitor or not monitor.scale or monitor.scale <= 0 then
|
||||
return
|
||||
end
|
||||
|
||||
-- Monitor dimensions are in physical pixels; gaps are logical, so the scale
|
||||
-- has to come out before the reserved area (already logical) comes off.
|
||||
local reserved = monitor.reserved
|
||||
local usable = monitor.height / monitor.scale - reserved.top - reserved.bottom
|
||||
|
||||
cover(math.max(0, math.floor(usable * (1 - share))))
|
||||
end
|
||||
|
||||
-- Until a monitor can be read, cover the whole work area rather than leaving
|
||||
-- the console unruled, so it is never seeded without its placement.
|
||||
cover(0)
|
||||
fit()
|
||||
|
||||
hl.on("monitor.layout_changed", fit)
|
||||
hl.on("monitor.focused", fit)
|
||||
|
||||
-- The direction names the edge the offset is measured from, not where the
|
||||
-- workspace goes: "slide top" drops it down into view, and "slide bottom"
|
||||
-- retracts it back up the way a Quake console does.
|
||||
hl.animation({ leaf = "specialWorkspaceIn", enabled = true, speed = 3, bezier = "easeOutQuint", style = "slide top" })
|
||||
hl.animation({ leaf = "specialWorkspaceOut", enabled = true, speed = 2, bezier = "easeInOutCubic", style = "slide bottom" })
|
||||
@@ -12,6 +12,7 @@ fcitx5-configtool
|
||||
fcitx5-wayland-launcher
|
||||
foot-server
|
||||
footclient
|
||||
hermes
|
||||
java-java-openjdk
|
||||
jconsole-java-openjdk
|
||||
jshell-java-openjdk
|
||||
|
||||
+109
-113
@@ -4,9 +4,8 @@
|
||||
// IDs are object keys. Dotted IDs imply hierarchy: trigger.share.file belongs
|
||||
// under trigger.share.
|
||||
// Kind is inferred: action -> action, target -> link, otherwise submenu.
|
||||
// Dotted IDs define the tree. provider:"name" points a submenu at one of
|
||||
// the shell's providers (the providers map in shell/plugins/menu/Menu.qml);
|
||||
// new provider names cannot be declared here. See docs/menu.md.
|
||||
// Dotted IDs define the tree. Use provider:"name" only when the submenu
|
||||
// calls provider_name() or a command named "name" to return JSON rows.
|
||||
// Optional fields:
|
||||
// aliases alternate `omarchy menu summon <name>` routes; also searchable.
|
||||
// Reserved for established names users already type; new entries
|
||||
@@ -15,10 +14,6 @@
|
||||
// title header text shown when the submenu is open; defaults to label
|
||||
// when shell condition; hide row when it fails
|
||||
// checked shell condition; append ✓ when it succeeds
|
||||
// disabled shell condition; when it succeeds the row stays listed but
|
||||
// goes dim, takes a ✓ and can no longer be selected. Install
|
||||
// rows use it so software already on the machine reads as
|
||||
// installed instead of vanishing from the list.
|
||||
|
||||
// Root Menu
|
||||
"apps": {"icon":"","label":"Apps","aliases":["app","applications"],"provider":"apps"},
|
||||
@@ -135,40 +130,41 @@
|
||||
"setup.network.qr": {"icon":"","label":"QR Code","aliases":["wifi-qr"],"when":"[[ $(omarchy-network-status) == wifi* ]]","action":"omarchy-shell shell summon omarchy.wifiqr"},
|
||||
"setup.default": {"icon":"","label":"Defaults","aliases":["default","defaults"]},
|
||||
"setup.default.agent": {"icon":"","label":"Agent","title":"Default Agent"},
|
||||
"setup.default.agent.agy": {"icon":"","label":"Antigravity","checked":"[[ \"$(omarchy-default-agent)\" == \"agy\" ]]","action":"omarchy-default-agent agy"},
|
||||
"setup.default.agent.claude": {"icon":"","label":"Claude","checked":"[[ \"$(omarchy-default-agent)\" == \"claude\" ]]","action":"omarchy-default-agent claude"},
|
||||
"setup.default.agent.codex": {"icon":"","iconFont":"omarchy","label":"Codex","checked":"[[ \"$(omarchy-default-agent)\" == \"codex\" ]]","action":"omarchy-default-agent codex"},
|
||||
"setup.default.agent.copilot": {"icon":"","label":"Copilot","checked":"[[ \"$(omarchy-default-agent)\" == \"copilot\" ]]","action":"omarchy-default-agent copilot"},
|
||||
"setup.default.agent.crush": {"icon":"","label":"Crush","checked":"[[ \"$(omarchy-default-agent)\" == \"crush\" ]]","action":"omarchy-default-agent crush"},
|
||||
"setup.default.agent.dim": {"icon":"","iconFont":"omarchy","label":"Dim Agent","checked":"[[ \"$(omarchy-default-agent)\" == \"dim\" ]]","action":"omarchy-default-agent dim"},
|
||||
"setup.default.agent.cursor-agent": {"icon":"","iconFont":"omarchy","label":"Cursor CLI","checked":"[[ \"$(omarchy-default-agent)\" == \"cursor-agent\" ]]","action":"omarchy-default-agent cursor-agent"},
|
||||
"setup.default.agent.gemini": {"icon":"","label":"Gemini","checked":"[[ \"$(omarchy-default-agent)\" == \"gemini\" ]]","action":"omarchy-default-agent gemini"},
|
||||
"setup.default.agent.grok": {"icon":"","iconFont":"omarchy","label":"Grok","checked":"[[ \"$(omarchy-default-agent)\" == \"grok\" ]]","action":"omarchy-default-agent grok"},
|
||||
"setup.default.agent.kimi": {"icon":"","iconFont":"omarchy","label":"Kimi Code","checked":"[[ \"$(omarchy-default-agent)\" == \"kimi\" ]]","action":"omarchy-default-agent kimi"},
|
||||
"setup.default.agent.hermes": {"icon":"","iconFont":"omarchy","label":"Hermes","checked":"[[ \"$(omarchy-default-agent)\" == \"hermes\" ]]","action":"omarchy-default-agent hermes"},
|
||||
"setup.default.agent.muse": {"icon":"","label":"Muse Code","checked":"[[ \"$(omarchy-default-agent)\" == \"muse\" ]]","action":"omarchy-default-agent muse"},
|
||||
"setup.default.agent.omp": {"icon":"","iconFont":"omarchy","label":"omp","checked":"[[ \"$(omarchy-default-agent)\" == \"omp\" ]]","action":"omarchy-default-agent omp"},
|
||||
"setup.default.agent.openclaw": {"icon":"","iconFont":"omarchy","label":"OpenClaw","checked":"[[ \"$(omarchy-default-agent)\" == \"openclaw\" ]]","action":"omarchy-default-agent openclaw"},
|
||||
"setup.default.agent.opencode": {"icon":"","iconFont":"omarchy","label":"OpenCode","checked":"[[ \"$(omarchy-default-agent)\" == \"opencode\" ]]","action":"omarchy-default-agent opencode"},
|
||||
"setup.default.agent.ori": {"icon":"","iconFont":"omarchy","label":"Ori","checked":"[[ \"$(omarchy-default-agent)\" == \"ori\" ]]","action":"omarchy-default-agent ori"},
|
||||
"setup.default.agent.pi": {"icon":"","iconFont":"omarchy","label":"Pi","checked":"[[ \"$(omarchy-default-agent)\" == \"pi\" ]]","action":"omarchy-default-agent pi"},
|
||||
"setup.default.browser": {"icon":"","label":"Browser","title":"Default Browser"},
|
||||
"setup.default.browser.chromium": {"icon":"","label":"Chromium","checked":"[[ \"$(omarchy-default-browser)\" == \"chromium\" ]]","action":"omarchy-default-browser chromium"},
|
||||
"setup.default.browser.chrome": {"icon":"","label":"Chrome","checked":"[[ \"$(omarchy-default-browser)\" == \"chrome\" ]]","action":"omarchy-default-browser chrome"},
|
||||
"setup.default.browser.brave": {"icon":"","label":"Brave","checked":"[[ \"$(omarchy-default-browser)\" == \"brave\" ]]","action":"omarchy-default-browser brave"},
|
||||
"setup.default.browser.brave-origin": {"icon":"","label":"Brave Origin","checked":"[[ \"$(omarchy-default-browser)\" == \"brave-origin\" ]]","action":"omarchy-default-browser brave-origin"},
|
||||
"setup.default.browser.edge": {"icon":"","label":"Edge","checked":"[[ \"$(omarchy-default-browser)\" == \"edge\" ]]","action":"omarchy-default-browser edge"},
|
||||
"setup.default.browser.firefox": {"icon":"","label":"Firefox","checked":"[[ \"$(omarchy-default-browser)\" == \"firefox\" ]]","action":"omarchy-default-browser firefox"},
|
||||
"setup.default.browser.zen": {"icon":"","label":"Zen","checked":"[[ \"$(omarchy-default-browser)\" == \"zen\" ]]","action":"omarchy-default-browser zen"},
|
||||
"setup.default.browser.chromium": {"icon":"","label":"Chromium","when":"omarchy-cmd-present chromium","checked":"[[ \"$(omarchy-default-browser)\" == \"chromium\" ]]","action":"omarchy-default-browser chromium"},
|
||||
"setup.default.browser.chrome": {"icon":"","label":"Chrome","when":"omarchy-cmd-present google-chrome-stable","checked":"[[ \"$(omarchy-default-browser)\" == \"chrome\" ]]","action":"omarchy-default-browser chrome"},
|
||||
"setup.default.browser.brave": {"icon":"","label":"Brave","when":"omarchy-cmd-present brave","checked":"[[ \"$(omarchy-default-browser)\" == \"brave\" ]]","action":"omarchy-default-browser brave"},
|
||||
"setup.default.browser.brave-origin": {"icon":"","label":"Brave Origin","when":"omarchy-cmd-present brave-origin","checked":"[[ \"$(omarchy-default-browser)\" == \"brave-origin\" ]]","action":"omarchy-default-browser brave-origin"},
|
||||
"setup.default.browser.edge": {"icon":"","label":"Edge","when":"omarchy-cmd-present microsoft-edge-stable","checked":"[[ \"$(omarchy-default-browser)\" == \"edge\" ]]","action":"omarchy-default-browser edge"},
|
||||
"setup.default.browser.firefox": {"icon":"","label":"Firefox","when":"omarchy-cmd-present firefox","checked":"[[ \"$(omarchy-default-browser)\" == \"firefox\" ]]","action":"omarchy-default-browser firefox"},
|
||||
"setup.default.browser.zen": {"icon":"","label":"Zen","when":"omarchy-cmd-present zen-browser","checked":"[[ \"$(omarchy-default-browser)\" == \"zen\" ]]","action":"omarchy-default-browser zen"},
|
||||
"setup.default.terminal": {"icon":"","label":"Terminal","title":"Default Terminal"},
|
||||
"setup.default.terminal.alacritty": {"icon":"","label":"Alacritty","checked":"[[ \"$(omarchy-default-terminal)\" == \"alacritty\" ]]","action":"omarchy-default-terminal alacritty"},
|
||||
"setup.default.terminal.foot": {"icon":"","label":"Foot","checked":"[[ \"$(omarchy-default-terminal)\" == \"foot\" ]]","action":"omarchy-default-terminal foot"},
|
||||
"setup.default.terminal.ghostty": {"icon":"","label":"Ghostty","checked":"[[ \"$(omarchy-default-terminal)\" == \"ghostty\" ]]","action":"omarchy-default-terminal ghostty"},
|
||||
"setup.default.terminal.kitty": {"icon":"","label":"Kitty","checked":"[[ \"$(omarchy-default-terminal)\" == \"kitty\" ]]","action":"omarchy-default-terminal kitty"},
|
||||
"setup.default.terminal.alacritty": {"icon":"","label":"Alacritty","when":"omarchy-cmd-present alacritty","checked":"[[ \"$(omarchy-default-terminal)\" == \"alacritty\" ]]","action":"omarchy-default-terminal alacritty"},
|
||||
"setup.default.terminal.foot": {"icon":"","label":"Foot","when":"omarchy-cmd-present foot","checked":"[[ \"$(omarchy-default-terminal)\" == \"foot\" ]]","action":"omarchy-default-terminal foot"},
|
||||
"setup.default.terminal.ghostty": {"icon":"","label":"Ghostty","when":"omarchy-cmd-present ghostty","checked":"[[ \"$(omarchy-default-terminal)\" == \"ghostty\" ]]","action":"omarchy-default-terminal ghostty"},
|
||||
"setup.default.terminal.kitty": {"icon":"","label":"Kitty","when":"omarchy-cmd-present kitty","checked":"[[ \"$(omarchy-default-terminal)\" == \"kitty\" ]]","action":"omarchy-default-terminal kitty"},
|
||||
"setup.default.editor": {"icon":"","label":"Editor","title":"Default Editor"},
|
||||
"setup.default.editor.neovim": {"icon":"","label":"Neovim","checked":"[[ \"$(omarchy-default-editor)\" == \"nvim\" ]]","action":"omarchy-default-editor nvim"},
|
||||
"setup.default.editor.vscode": {"icon":"","label":"VSCode","checked":"[[ \"$(omarchy-default-editor)\" == \"code\" ]]","action":"omarchy-default-editor code"},
|
||||
"setup.default.editor.cursor": {"icon":"","label":"Cursor","checked":"[[ \"$(omarchy-default-editor)\" == \"cursor\" ]]","action":"omarchy-default-editor cursor"},
|
||||
"setup.default.editor.zed": {"icon":"","label":"Zed","checked":"[[ \"$(omarchy-default-editor)\" == \"zeditor\" ]]","action":"omarchy-default-editor zed"},
|
||||
"setup.default.editor.sublime": {"icon":"","label":"Sublime Text","checked":"[[ \"$(omarchy-default-editor)\" == \"sublime_text\" ]]","action":"omarchy-default-editor sublime_text"},
|
||||
"setup.default.editor.helix": {"icon":"","label":"Helix","checked":"[[ \"$(omarchy-default-editor)\" == \"helix\" ]]","action":"omarchy-default-editor helix"},
|
||||
"setup.default.editor.vim": {"icon":"","label":"Vim","checked":"[[ \"$(omarchy-default-editor)\" == \"vim\" ]]","action":"omarchy-default-editor vim"},
|
||||
"setup.default.editor.emacs": {"icon":"","label":"Emacs","checked":"[[ \"$(omarchy-default-editor)\" == \"emacs\" ]]","action":"omarchy-default-editor emacs"},
|
||||
"setup.default.editor.neovim": {"icon":"","label":"Neovim","when":"omarchy-cmd-present nvim","checked":"[[ \"$(omarchy-default-editor)\" == \"nvim\" ]]","action":"omarchy-default-editor nvim"},
|
||||
"setup.default.editor.vscode": {"icon":"","label":"VSCode","when":"omarchy-cmd-present code","checked":"[[ \"$(omarchy-default-editor)\" == \"code\" ]]","action":"omarchy-default-editor code"},
|
||||
"setup.default.editor.cursor": {"icon":"","iconFont":"omarchy","label":"Cursor","when":"omarchy-cmd-present cursor","checked":"[[ \"$(omarchy-default-editor)\" == \"cursor\" ]]","action":"omarchy-default-editor cursor"},
|
||||
"setup.default.editor.zed": {"icon":"","label":"Zed","when":"omarchy-cmd-present zeditor","checked":"[[ \"$(omarchy-default-editor)\" == \"zeditor\" ]]","action":"omarchy-default-editor zed"},
|
||||
"setup.default.editor.sublime": {"icon":"","label":"Sublime Text","when":"omarchy-cmd-present sublime_text","checked":"[[ \"$(omarchy-default-editor)\" == \"sublime_text\" ]]","action":"omarchy-default-editor sublime_text"},
|
||||
"setup.default.editor.helix": {"icon":"","label":"Helix","when":"omarchy-cmd-present helix","checked":"[[ \"$(omarchy-default-editor)\" == \"helix\" ]]","action":"omarchy-default-editor helix"},
|
||||
"setup.default.editor.vim": {"icon":"","label":"Vim","when":"omarchy-cmd-present vim","checked":"[[ \"$(omarchy-default-editor)\" == \"vim\" ]]","action":"omarchy-default-editor vim"},
|
||||
"setup.default.editor.emacs": {"icon":"","label":"Emacs","when":"omarchy-cmd-present emacs","checked":"[[ \"$(omarchy-default-editor)\" == \"emacs\" ]]","action":"omarchy-default-editor emacs"},
|
||||
"setup.plugin": {"icon":"","label":"Plugins","aliases":["plugin","plugins"]},
|
||||
"setup.plugin.enable": {"icon":"","label":"Enable Plugin","action":"omarchy-menu-plugin enable"},
|
||||
"setup.plugin.disable": {"icon":"","label":"Disable Plugin","action":"omarchy-menu-plugin disable"},
|
||||
@@ -191,10 +187,8 @@
|
||||
// Install
|
||||
"install.package": {"icon":"","label":"Package","action":"xdg-terminal-exec --app-id=org.omarchy.terminal omarchy-pkg-install"},
|
||||
"install.aur": {"icon":"","label":"AUR","action":"xdg-terminal-exec --app-id=org.omarchy.terminal omarchy-pkg-aur-install"},
|
||||
"install.ai": {"icon":"","label":"AI"},
|
||||
"install.service": {"icon":"","label":"Service"},
|
||||
"install.development": {"icon":"","label":"Development"},
|
||||
"install.editor": {"icon":"","label":"Editor"},
|
||||
"install.webapp": {"icon":"","label":"Web App","action":"omarchy-launch-floating-terminal-with-presentation omarchy-webapp-install"},
|
||||
"install.tui": {"icon":"","label":"TUI","action":"omarchy-launch-floating-terminal-with-presentation omarchy-tui-install"},
|
||||
"install.style": {"icon":"","label":"Style"},
|
||||
"install.style.theme": {"icon":"","label":"Theme","action":"omarchy-launch-floating-terminal-with-presentation omarchy-theme-install"},
|
||||
"install.style.background": {"icon":"","label":"Background","action":"omarchy-theme-bg-install"},
|
||||
@@ -205,90 +199,93 @@
|
||||
"install.style.font.victor": {"icon":"","label":"Victor Code","action":"omarchy-install-font 'Victor Code' ttf-victor-mono-nerd 'VictorMono Nerd Font'"},
|
||||
"install.style.font.bitstream": {"icon":"","label":"Bitstream Vera Mono","action":"omarchy-install-font 'Bitstream Vera Code' ttf-bitstream-vera-mono-nerd 'BitstromWera Nerd Font'"},
|
||||
"install.style.font.iosevka": {"icon":"","label":"Iosevka","action":"omarchy-install-font Iosevka ttf-iosevka-nerd 'Iosevka Nerd Font Mono'"},
|
||||
"install.gaming": {"icon":"","label":"Gaming"},
|
||||
"install.browser": {"icon":"","label":"Browser"},
|
||||
"install.webapp": {"icon":"","label":"Web App","action":"omarchy-launch-floating-terminal-with-presentation omarchy-webapp-install"},
|
||||
"install.service": {"icon":"","label":"Service"},
|
||||
"install.development": {"icon":"","label":"Development"},
|
||||
"install.editor": {"icon":"","label":"Editor"},
|
||||
"install.terminal": {"icon":"","label":"Terminal"},
|
||||
"install.tui": {"icon":"","label":"TUI","action":"omarchy-launch-floating-terminal-with-presentation omarchy-tui-install"},
|
||||
"install.windows": {"icon":"","label":"Windows","disabled":"[[ -f $HOME/.local/share/applications/windows-vm.desktop ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-windows-vm install'"},
|
||||
"install.preinstalls": {"icon":"","label":"Preinstalls","disabled":"[[ ! -f $HOME/.local/state/omarchy/preinstalls-removed ]]","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-preinstalls"},
|
||||
"install.browser.chrome": {"icon":"","label":"Chrome","disabled":"omarchy-pkg-present google-chrome","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-browser chrome'"},
|
||||
"install.browser.edge": {"icon":"","label":"Edge","disabled":"omarchy-pkg-present microsoft-edge-stable-bin","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-browser edge'"},
|
||||
"install.browser.brave": {"icon":"","label":"Brave","disabled":"omarchy-pkg-present brave-bin","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-browser brave'"},
|
||||
"install.browser.brave-origin": {"icon":"","label":"Brave Origin","disabled":"omarchy-pkg-present brave-origin-bin","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-browser brave-origin'"},
|
||||
"install.browser.firefox": {"icon":"","label":"Firefox","disabled":"omarchy-pkg-present firefox","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-browser firefox'"},
|
||||
"install.browser.zen": {"icon":"","label":"Zen","disabled":"omarchy-pkg-present zen-browser-bin","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-browser zen'"},
|
||||
"install.service.1password": {"icon":"","label":"1Password","disabled":"omarchy-pkg-present 1password","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-service-1password"},
|
||||
"install.service.dropbox": {"icon":"","label":"Dropbox","disabled":"omarchy-pkg-present dropbox","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-service-dropbox"},
|
||||
"install.service.spotify": {"icon":"","label":"Spotify","disabled":"omarchy-pkg-present spotify","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-service-spotify"},
|
||||
"install.service.signal": {"icon":"","label":"Signal","disabled":"omarchy-pkg-present signal-desktop","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-service-signal"},
|
||||
"install.service.tailscale": {"icon":"","label":"Tailscale","disabled":"omarchy-pkg-present tailscale","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-service-tailscale"},
|
||||
"install.service.nordvpn": {"icon":"","label":"NordVPN","disabled":"omarchy-pkg-present nordvpn-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-service-nordvpn"},
|
||||
"install.service.once": {"icon":"","label":"ONCE","disabled":"omarchy-pkg-present once-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-service-once"},
|
||||
"install.service.bitwarden": {"icon":"","label":"Bitwarden","disabled":"omarchy-pkg-present bitwarden","action":"omarchy-install-and-launch Bitwarden 'bitwarden bitwarden-cli' bitwarden"},
|
||||
"install.service.chromium-account": {"icon":"","label":"Chromium Account","when":"[[ -f ~/.config/chromium-flags.conf ]]","disabled":"grep -q oauth2-client-id ~/.config/chromium-flags.conf","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-chromium-google-account"},
|
||||
"install.editor.vscode": {"icon":"","label":"VSCode","disabled":"omarchy-pkg-present visual-studio-code-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-editor-vscode"},
|
||||
"install.editor.cursor": {"icon":"","label":"Cursor","disabled":"omarchy-pkg-present cursor-bin","action":"omarchy-install-and-launch Cursor cursor-bin cursor"},
|
||||
"install.editor.zed": {"icon":"","label":"Zed","disabled":"omarchy-pkg-present zed","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-editor-zed"},
|
||||
"install.editor.sublime": {"icon":"","label":"Sublime Text","disabled":"omarchy-pkg-present sublime-text-4","action":"omarchy-install-and-launch 'Sublime Text' sublime-text-4 sublime_text"},
|
||||
"install.editor.helix": {"icon":"","label":"Helix","disabled":"omarchy-pkg-present helix","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-editor-helix"},
|
||||
"install.editor.vim": {"icon":"","label":"Vim","disabled":"omarchy-pkg-present vim","action":"omarchy-install-app Vim vim"},
|
||||
"install.editor.emacs": {"icon":"","label":"Emacs","disabled":"omarchy-pkg-present omarchy-emacs","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-editor-emacs"},
|
||||
"install.terminal.alacritty": {"icon":"","label":"Alacritty","disabled":"omarchy-pkg-present alacritty","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-terminal alacritty'"},
|
||||
"install.terminal.foot": {"icon":"","label":"Foot","disabled":"omarchy-pkg-present foot","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-terminal foot'"},
|
||||
"install.terminal.ghostty": {"icon":"","label":"Ghostty","disabled":"omarchy-pkg-present ghostty","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-terminal ghostty'"},
|
||||
"install.terminal.kitty": {"icon":"","label":"Kitty","disabled":"omarchy-pkg-present kitty","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-terminal kitty'"},
|
||||
"install.ai.chatgpt": {"icon":"","iconFont":"omarchy","label":"ChatGPT Desktop","disabled":"omarchy-pkg-present openai-codex-desktop","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-ai-chatgpt"},
|
||||
"install.ai.dictation": {"icon":"","label":"Dictation","disabled":"omarchy-pkg-present voxtype-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-voxtype-install"},
|
||||
"install.ai.dim": {"icon":"","iconFont":"omarchy","label":"Dim Agent","disabled":"mise where npm:dimcode &>/dev/null","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-mise-install npm:dimcode dim && dim'"},
|
||||
"install.ai.grok-bot": {"icon":"","iconFont":"omarchy","label":"Grok Bot","disabled":"omarchy-pkg-present grok-bot","action":"omarchy-install-and-launch 'Grok Bot' grok-bot grok-bot"},
|
||||
"install.ai.kimi-code": {"icon":"","iconFont":"omarchy","label":"Kimi Code","disabled":"mise where npm:@moonshot-ai/kimi-code &>/dev/null","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-mise-install npm:@moonshot-ai/kimi-code kimi && kimi'"},
|
||||
"install.ai.lm-studio": {"icon":"","iconFont":"omarchy","label":"LM Studio","disabled":"omarchy-pkg-present lmstudio-bin","action":"omarchy-install-app 'LM Studio' lmstudio-bin"},
|
||||
"install.ai.ollama": {"icon":"","iconFont":"omarchy","label":"Ollama","disabled":"omarchy-cmd-present ollama","action":"if omarchy-cmd-present nvidia-smi; then ollama_pkg=ollama-cuda; elif omarchy-cmd-present rocminfo; then ollama_pkg=ollama-rocm; else ollama_pkg=ollama; fi; omarchy-install-app Ollama \"$ollama_pkg\""},
|
||||
"install.ai.t3-code": {"icon":"","iconFont":"omarchy","label":"T3 Code","disabled":"omarchy-pkg-present t3code-bin","action":"omarchy-install-and-launch 'T3 Code' t3code-bin t3code"},
|
||||
"install.gaming.steam": {"icon":"","label":"Steam","disabled":"omarchy-pkg-present steam","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-gaming-steam"},
|
||||
"install.gaming.retroarch": {"icon":"","label":"RetroArch","disabled":"omarchy-pkg-present retroarch","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-gaming-retroarch"},
|
||||
"install.gaming.minecraft": {"icon":"","label":"Minecraft","disabled":"omarchy-pkg-present minecraft-launcher","action":"omarchy-install-and-launch Minecraft minecraft-launcher minecraft-launcher"},
|
||||
"install.gaming.geforce-now": {"icon":"","label":"NVIDIA GeForce NOW","disabled":"flatpak info com.nvidia.geforcenow","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-gaming-geforce-now"},
|
||||
"install.gaming.xbox-cloud": {"icon":"","label":"Xbox Cloud Gaming","disabled":"[[ -f \"$HOME/.local/share/applications/Xbox Cloud Gaming.desktop\" ]]","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-gaming-xbox-cloud"},
|
||||
"install.gaming.xbox-controllers": {"icon":"","label":"Xbox Controllers","disabled":"omarchy-pkg-present xpadneo-dkms","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-gaming-xbox-controllers"},
|
||||
"install.gaming.battlenet": {"icon":"","label":"Battle.net","disabled":"[[ -d $HOME/Games/battlenet ]]","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-gaming-battlenet"},
|
||||
"install.gaming.lutris": {"icon":"","label":"Lutris","disabled":"omarchy-pkg-present lutris","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-gaming-lutris"},
|
||||
"install.gaming.heroic": {"icon":"","label":"Heroic (Epic Games)","disabled":"omarchy-pkg-present heroic-games-launcher-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-gaming-heroic"},
|
||||
"install.browser": {"icon":"","label":"Browser"},
|
||||
"install.ai": {"icon":"","label":"AI"},
|
||||
"install.gaming": {"icon":"","label":"Gaming"},
|
||||
"install.windows": {"icon":"","label":"Windows","when":"[[ ! -f $HOME/.local/share/applications/windows-vm.desktop ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-windows-vm install'"},
|
||||
"install.preinstalls": {"icon":"","label":"Preinstalls","when":"[[ -f $HOME/.local/state/omarchy/preinstalls-removed ]]","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-preinstalls"},
|
||||
"install.browser.chrome": {"icon":"","label":"Chrome","when":"! omarchy-pkg-present google-chrome","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-browser chrome'"},
|
||||
"install.browser.edge": {"icon":"","label":"Edge","when":"! omarchy-pkg-present microsoft-edge-stable-bin","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-browser edge'"},
|
||||
"install.browser.brave": {"icon":"","label":"Brave","when":"! omarchy-pkg-present brave-bin","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-browser brave'"},
|
||||
"install.browser.brave-origin": {"icon":"","label":"Brave Origin","when":"! omarchy-pkg-present brave-origin-bin","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-browser brave-origin'"},
|
||||
"install.browser.firefox": {"icon":"","label":"Firefox","when":"! omarchy-pkg-present firefox","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-browser firefox'"},
|
||||
"install.browser.zen": {"icon":"","label":"Zen","when":"! omarchy-pkg-present zen-browser-bin","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-browser zen'"},
|
||||
"install.service.1password": {"icon":"","label":"1Password","when":"! omarchy-pkg-present 1password","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-service-1password"},
|
||||
"install.service.dropbox": {"icon":"","label":"Dropbox","when":"! omarchy-pkg-present dropbox","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-service-dropbox"},
|
||||
"install.service.spotify": {"icon":"","label":"Spotify","when":"! omarchy-pkg-present spotify","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-service-spotify"},
|
||||
"install.service.signal": {"icon":"","label":"Signal","when":"! omarchy-pkg-present signal-desktop","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-service-signal"},
|
||||
"install.service.tailscale": {"icon":"","label":"Tailscale","when":"! omarchy-pkg-present tailscale","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-service-tailscale"},
|
||||
"install.service.nordvpn": {"icon":"","label":"NordVPN","when":"! omarchy-pkg-present nordvpn-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-service-nordvpn"},
|
||||
"install.service.once": {"icon":"","label":"ONCE","when":"! omarchy-pkg-present once-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-service-once"},
|
||||
"install.service.bitwarden": {"icon":"","label":"Bitwarden","when":"! omarchy-pkg-present bitwarden","action":"omarchy-install-and-launch Bitwarden 'bitwarden bitwarden-cli' bitwarden"},
|
||||
"install.service.chromium-account": {"icon":"","label":"Chromium Account","when":"[[ -f ~/.config/chromium-flags.conf ]] && ! grep -q oauth2-client-id ~/.config/chromium-flags.conf","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-chromium-google-account"},
|
||||
"install.editor.vscode": {"icon":"","label":"VSCode","when":"! omarchy-pkg-present visual-studio-code-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-editor-vscode"},
|
||||
"install.editor.cursor": {"icon":"","iconFont":"omarchy","label":"Cursor","when":"! omarchy-pkg-present cursor-bin","action":"omarchy-install-and-launch Cursor cursor-bin cursor"},
|
||||
"install.editor.zed": {"icon":"","label":"Zed","when":"! omarchy-pkg-present zed","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-editor-zed"},
|
||||
"install.editor.sublime": {"icon":"","label":"Sublime Text","when":"! omarchy-pkg-present sublime-text-4","action":"omarchy-install-and-launch 'Sublime Text' sublime-text-4 sublime_text"},
|
||||
"install.editor.helix": {"icon":"","label":"Helix","when":"! omarchy-pkg-present helix","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-editor-helix"},
|
||||
"install.editor.vim": {"icon":"","label":"Vim","when":"! omarchy-pkg-present vim","action":"omarchy-install-app Vim vim"},
|
||||
"install.editor.emacs": {"icon":"","label":"Emacs","when":"! omarchy-pkg-present omarchy-emacs","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-editor-emacs"},
|
||||
"install.terminal.alacritty": {"icon":"","label":"Alacritty","when":"! omarchy-pkg-present alacritty","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-terminal alacritty'"},
|
||||
"install.terminal.foot": {"icon":"","label":"Foot","when":"! omarchy-pkg-present foot","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-terminal foot'"},
|
||||
"install.terminal.ghostty": {"icon":"","label":"Ghostty","when":"! omarchy-pkg-present ghostty","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-terminal ghostty'"},
|
||||
"install.terminal.kitty": {"icon":"","label":"Kitty","when":"! omarchy-pkg-present kitty","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-terminal kitty'"},
|
||||
"install.ai.chatgpt": {"icon":"","iconFont":"omarchy","label":"ChatGPT Desktop","when":"! omarchy-pkg-present openai-codex-desktop","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-ai-chatgpt"},
|
||||
"install.ai.dictation": {"icon":"","label":"Dictation","when":"! omarchy-pkg-present voxtype-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-voxtype-install"},
|
||||
"install.ai.grok-bot": {"icon":"","iconFont":"omarchy","label":"Grok Bot","when":"! omarchy-pkg-present grok-bot","action":"omarchy-install-and-launch 'Grok Bot' grok-bot grok-bot"},
|
||||
"install.ai.lm-studio": {"icon":"","iconFont":"omarchy","label":"LM Studio","when":"! omarchy-pkg-present lmstudio-bin","action":"omarchy-install-app 'LM Studio' lmstudio-bin"},
|
||||
"install.ai.ollama": {"icon":"","iconFont":"omarchy","label":"Ollama","when":"! omarchy-cmd-present ollama","action":"if omarchy-cmd-present nvidia-smi; then ollama_pkg=ollama-cuda; elif omarchy-cmd-present rocminfo; then ollama_pkg=ollama-rocm; else ollama_pkg=ollama; fi; omarchy-install-app Ollama \"$ollama_pkg\""},
|
||||
"install.ai.t3-code": {"icon":"","iconFont":"omarchy","label":"T3 Code","when":"! omarchy-pkg-present t3code-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-ai-t3-code"},
|
||||
"install.ai.hermes": {"icon":"","iconFont":"omarchy","label":"Hermes Desktop","when":"! omarchy-pkg-present hermes-desktop","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-ai-hermes"},
|
||||
"install.ai.openclaw": {"icon":"","iconFont":"omarchy","label":"OpenClaw","when":"! omarchy-pkg-present openclaw","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-ai-openclaw"},
|
||||
"install.ai.perplexity": {"icon":"","iconFont":"omarchy","label":"Perplexity","when":"! omarchy-pkg-present perplexity","action":"omarchy-install-and-launch Perplexity perplexity perplexity"},
|
||||
"install.gaming.steam": {"icon":"","label":"Steam","when":"! omarchy-pkg-present steam","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-gaming-steam"},
|
||||
"install.gaming.retroarch": {"icon":"","label":"RetroArch","when":"! omarchy-pkg-present retroarch","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-gaming-retroarch"},
|
||||
"install.gaming.minecraft": {"icon":"","label":"Minecraft","when":"! omarchy-pkg-present minecraft-launcher","action":"omarchy-install-and-launch Minecraft minecraft-launcher minecraft-launcher"},
|
||||
"install.gaming.geforce-now": {"icon":"","label":"NVIDIA GeForce NOW","when":"! flatpak info com.nvidia.geforcenow","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-gaming-geforce-now"},
|
||||
"install.gaming.xbox-cloud": {"icon":"","label":"Xbox Cloud Gaming","when":"[[ ! -f \"$HOME/.local/share/applications/Xbox Cloud Gaming.desktop\" ]]","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-gaming-xbox-cloud"},
|
||||
"install.gaming.xbox-controllers": {"icon":"","label":"Xbox Controllers","when":"! omarchy-pkg-present xpadneo-dkms","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-gaming-xbox-controllers"},
|
||||
"install.gaming.battlenet": {"icon":"","label":"Battle.net","when":"[[ ! -d $HOME/Games/battlenet ]]","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-gaming-battlenet"},
|
||||
"install.gaming.lutris": {"icon":"","label":"Lutris","when":"! omarchy-pkg-present lutris","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-gaming-lutris"},
|
||||
"install.gaming.heroic": {"icon":"","label":"Heroic (Epic Games)","when":"! omarchy-pkg-present heroic-games-launcher-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-gaming-heroic"},
|
||||
"install.gaming.retro-launcher": {"icon":"","label":"RetroArch Game Launcher","action":"omarchy-games-retro-install"},
|
||||
"install.development.rails": {"icon":"","label":"Ruby on Rails","disabled":"[[ -d $HOME/.local/share/mise/installs/ruby ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env ruby'"},
|
||||
"install.development.rails": {"icon":"","label":"Ruby on Rails","when":"[[ ! -d $HOME/.local/share/mise/installs/ruby ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env ruby'"},
|
||||
"install.development.docker-dbs": {"icon":"","label":"Docker DB","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-docker-dbs"},
|
||||
"install.development.javascript": {"icon":"","label":"JavaScript"},
|
||||
"install.development.go": {"icon":"","label":"Go","disabled":"[[ -d $HOME/.local/share/mise/installs/go ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env go'"},
|
||||
"install.development.go": {"icon":"","label":"Go","when":"[[ ! -d $HOME/.local/share/mise/installs/go ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env go'"},
|
||||
"install.development.php": {"icon":"","label":"PHP"},
|
||||
"install.development.python": {"icon":"","label":"Python","disabled":"[[ -d $HOME/.local/share/mise/installs/python ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env python'"},
|
||||
"install.development.python": {"icon":"","label":"Python","when":"[[ ! -d $HOME/.local/share/mise/installs/python ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env python'"},
|
||||
"install.development.elixir": {"icon":"","label":"Elixir"},
|
||||
"install.development.zig": {"icon":"","label":"Zig","disabled":"[[ -d $HOME/.local/share/mise/installs/zig ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env zig'"},
|
||||
"install.development.rust": {"icon":"","label":"Rust","disabled":"[[ -d $HOME/.rustup ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env rust'"},
|
||||
"install.development.java": {"icon":"","label":"Java","disabled":"[[ -d $HOME/.local/share/mise/installs/java ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env java'"},
|
||||
"install.development.dotnet": {"icon":"","label":".NET","disabled":"[[ -d $HOME/.local/share/mise/installs/dotnet ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env dotnet'"},
|
||||
"install.development.ocaml": {"icon":"","label":"OCaml","disabled":"[[ -d $HOME/.opam ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env ocaml'"},
|
||||
"install.development.clojure": {"icon":"","label":"Clojure","disabled":"[[ -d $HOME/.local/share/mise/installs/clojure ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env clojure'"},
|
||||
"install.development.scala": {"icon":"","label":"Scala","disabled":"[[ -d $HOME/.local/share/mise/installs/scala ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env scala'"},
|
||||
"install.development.javascript.node": {"icon":"","label":"Node.js","disabled":"[[ -d $HOME/.local/share/mise/installs/node ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env node'"},
|
||||
"install.development.javascript.bun": {"icon":"","label":"Bun","disabled":"[[ -d $HOME/.local/share/mise/installs/bun ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env bun'"},
|
||||
"install.development.javascript.deno": {"icon":"","label":"Deno","disabled":"[[ -d $HOME/.local/share/mise/installs/deno ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env deno'"},
|
||||
"install.development.php.php": {"icon":"","label":"PHP","disabled":"omarchy-pkg-present php","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env php'"},
|
||||
"install.development.php.laravel": {"icon":"","label":"Laravel","disabled":"[[ -x $HOME/.config/composer/vendor/bin/laravel ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env laravel'"},
|
||||
"install.development.php.symfony": {"icon":"","label":"Symfony","disabled":"omarchy-pkg-present symfony-cli","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env symfony'"},
|
||||
"install.development.elixir.elixir": {"icon":"","label":"Elixir","disabled":"[[ -d $HOME/.local/share/mise/installs/elixir ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env elixir'"},
|
||||
"install.development.elixir.phoenix": {"icon":"","label":"Phoenix","disabled":"compgen -G \"$HOME/.mix/archives/phx_new*\"","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env phoenix'"},
|
||||
"install.development.zig": {"icon":"","label":"Zig","when":"[[ ! -d $HOME/.local/share/mise/installs/zig ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env zig'"},
|
||||
"install.development.rust": {"icon":"","label":"Rust","when":"[[ ! -d $HOME/.rustup ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env rust'"},
|
||||
"install.development.java": {"icon":"","label":"Java","when":"[[ ! -d $HOME/.local/share/mise/installs/java ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env java'"},
|
||||
"install.development.dotnet": {"icon":"","label":".NET","when":"[[ ! -d $HOME/.local/share/mise/installs/dotnet ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env dotnet'"},
|
||||
"install.development.ocaml": {"icon":"","label":"OCaml","when":"[[ ! -d $HOME/.opam ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env ocaml'"},
|
||||
"install.development.clojure": {"icon":"","label":"Clojure","when":"[[ ! -d $HOME/.local/share/mise/installs/clojure ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env clojure'"},
|
||||
"install.development.scala": {"icon":"","label":"Scala","when":"[[ ! -d $HOME/.local/share/mise/installs/scala ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env scala'"},
|
||||
"install.development.javascript.node": {"icon":"","label":"Node.js","when":"[[ ! -d $HOME/.local/share/mise/installs/node ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env node'"},
|
||||
"install.development.javascript.bun": {"icon":"","label":"Bun","when":"[[ ! -d $HOME/.local/share/mise/installs/bun ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env bun'"},
|
||||
"install.development.javascript.deno": {"icon":"","label":"Deno","when":"[[ ! -d $HOME/.local/share/mise/installs/deno ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env deno'"},
|
||||
"install.development.php.php": {"icon":"","label":"PHP","when":"! omarchy-pkg-present php","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env php'"},
|
||||
"install.development.php.laravel": {"icon":"","label":"Laravel","when":"[[ ! -x $HOME/.config/composer/vendor/bin/laravel ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env laravel'"},
|
||||
"install.development.php.symfony": {"icon":"","label":"Symfony","when":"! omarchy-pkg-present symfony-cli","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env symfony'"},
|
||||
"install.development.elixir.elixir": {"icon":"","label":"Elixir","when":"[[ ! -d $HOME/.local/share/mise/installs/elixir ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env elixir'"},
|
||||
"install.development.elixir.phoenix": {"icon":"","label":"Phoenix","when":"! compgen -G \"$HOME/.mix/archives/phx_new*\"","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env phoenix'"},
|
||||
|
||||
// Remove
|
||||
"remove.package": {"icon":"","label":"Package","action":"xdg-terminal-exec --app-id=org.omarchy.terminal omarchy-pkg-remove"},
|
||||
"remove.ai": {"icon":"","label":"AI","title":"Remove"},
|
||||
"remove.service": {"icon":"","label":"Services","title":"Remove"},
|
||||
"remove.development": {"icon":"","label":"Development","title":"Remove"},
|
||||
"remove.theme": {"icon":"","label":"Theme","action":"omarchy-theme-remove"},
|
||||
"remove.gaming": {"icon":"","label":"Gaming","title":"Remove"},
|
||||
"remove.browser": {"icon":"","label":"Browser","title":"Remove"},
|
||||
"remove.webapp": {"icon":"","label":"Web App","when":"grep -qE '^Exec=.*(omarchy-launch-webapp|omarchy-webapp-handler)' $HOME/.local/share/applications/*.desktop","action":"omarchy-webapp-remove"},
|
||||
"remove.tui": {"icon":"","label":"TUI","when":"grep -qE '^Exec=.*(\\$TERMINAL|xdg-terminal-exec).*-e' $HOME/.local/share/applications/*.desktop","action":"omarchy-tui-remove"},
|
||||
"remove.development": {"icon":"","label":"Development","title":"Remove"},
|
||||
"remove.theme": {"icon":"","label":"Theme","action":"omarchy-theme-remove"},
|
||||
"remove.browser": {"icon":"","label":"Browser","title":"Remove"},
|
||||
"remove.dictation": {"icon":"","label":"Dictation","when":"omarchy-pkg-present voxtype-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-voxtype-remove"},
|
||||
"remove.gaming": {"icon":"","label":"Gaming","title":"Remove"},
|
||||
"remove.service": {"icon":"","label":"Services","title":"Remove"},
|
||||
"remove.windows": {"icon":"","label":"Windows","when":"[[ -f $HOME/.local/share/applications/windows-vm.desktop ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-windows-vm remove'"},
|
||||
"remove.preinstalls": {"icon":"","label":"Preinstalls","when":"[[ ! -f $HOME/.local/state/omarchy/preinstalls-removed ]]","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-preinstalls"},
|
||||
"remove.security": {"icon":"","label":"Security","title":"Remove"},
|
||||
@@ -296,6 +293,7 @@
|
||||
"remove.security.fido2": {"icon":"","label":"Fido2","when":"omarchy-pkg-present pam-u2f","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-security-fido2"},
|
||||
"remove.security.sshd": {"icon":"","label":"SSHD","when":"systemctl is-enabled --quiet sshd","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-security-sshd"},
|
||||
"remove.security.sudoless-docker": {"icon":"","label":"Sudoless Docker","when":"! omarchy-sudo-docker --configured","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-security-sudoless-docker"},
|
||||
"remove.ai.hermes": {"icon":"","iconFont":"omarchy","label":"Hermes Desktop","when":"omarchy-pkg-present hermes-desktop","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-ai-hermes"},
|
||||
"remove.browser.chrome": {"icon":"","label":"Chrome","when":"omarchy-pkg-present google-chrome","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-remove-browser chrome'"},
|
||||
"remove.browser.edge": {"icon":"","label":"Edge","when":"omarchy-pkg-present microsoft-edge-stable-bin","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-remove-browser edge'"},
|
||||
"remove.browser.brave": {"icon":"","label":"Brave","when":"omarchy-pkg-present brave-bin","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-remove-browser brave'"},
|
||||
@@ -304,12 +302,6 @@
|
||||
"remove.browser.zen": {"icon":"","label":"Zen","when":"omarchy-pkg-present zen-browser-bin","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-remove-browser zen'"},
|
||||
"remove.service.dropbox": {"icon":"","label":"Dropbox","when":"omarchy-pkg-present dropbox","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-service-dropbox"},
|
||||
"remove.service.tailscale": {"icon":"","label":"Tailscale","when":"omarchy-pkg-present tailscale","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-service-tailscale"},
|
||||
"remove.ai.chatgpt": {"icon":"","iconFont":"omarchy","label":"ChatGPT Desktop","when":"omarchy-pkg-present openai-codex-desktop","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-ai-chatgpt"},
|
||||
"remove.ai.dictation": {"icon":"","label":"Dictation","when":"omarchy-pkg-present voxtype-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-voxtype-remove"},
|
||||
"remove.ai.grok-bot": {"icon":"","iconFont":"omarchy","label":"Grok Bot","when":"omarchy-pkg-present grok-bot","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-ai-grok-bot"},
|
||||
"remove.ai.lm-studio": {"icon":"","iconFont":"omarchy","label":"LM Studio","when":"omarchy-pkg-present lmstudio-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-ai-lm-studio"},
|
||||
"remove.ai.ollama": {"icon":"","iconFont":"omarchy","label":"Ollama","when":"omarchy-pkg-present ollama","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-ai-ollama"},
|
||||
"remove.ai.t3-code": {"icon":"","iconFont":"omarchy","label":"T3 Code","when":"omarchy-pkg-present t3code-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-ai-t3-code"},
|
||||
"remove.gaming.steam": {"icon":"","label":"Steam","when":"omarchy-pkg-present steam","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-gaming-steam"},
|
||||
"remove.gaming.retroarch": {"icon":"","label":"RetroArch","when":"omarchy-pkg-present retroarch","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-gaming-retroarch"},
|
||||
"remove.gaming.minecraft": {"icon":"","label":"Minecraft","when":"omarchy-pkg-present minecraft-launcher","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-gaming-minecraft"},
|
||||
@@ -340,6 +332,10 @@
|
||||
"remove.development.php.symfony": {"icon":"","label":"Symfony","when":"omarchy-pkg-present symfony-cli","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-remove-dev-env symfony'"},
|
||||
"remove.development.elixir.elixir": {"icon":"","label":"Elixir","when":"[[ -d $HOME/.local/share/mise/installs/elixir ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-remove-dev-env elixir'"},
|
||||
"remove.development.elixir.phoenix": {"icon":"","label":"Phoenix","when":"[[ -d $HOME/.local/share/mise/installs/elixir ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-remove-dev-env phoenix'"},
|
||||
"remove.ai": {"icon":"","label":"AI","title":"Remove"},
|
||||
"remove.ai.t3-code": {"icon":"","iconFont":"omarchy","label":"T3 Code","when":"omarchy-pkg-present t3code-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-ai-t3-code"},
|
||||
"remove.ai.openclaw": {"icon":"","iconFont":"omarchy","label":"OpenClaw","when":"omarchy-pkg-present openclaw","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-ai-openclaw"},
|
||||
"remove.ai.perplexity": {"icon":"","iconFont":"omarchy","label":"Perplexity","when":"omarchy-pkg-present perplexity","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-ai-perplexity"},
|
||||
|
||||
// Update
|
||||
"update.omarchy": {"icon":"","iconFont":"omarchy","label":"Omarchy","action":"omarchy-launch-floating-terminal-with-presentation omarchy-update"},
|
||||
|
||||
@@ -26,4 +26,4 @@ Include = /etc/pacman.d/mirrorlist
|
||||
Include = /etc/pacman.d/mirrorlist
|
||||
|
||||
[omarchy]
|
||||
Server = https://pkgs.omarchy.org/edge/$arch
|
||||
Server = https://pkgs.omarchy.org/rc/$arch
|
||||
@@ -1,29 +1,65 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Use the Vfio to Integrated trick to turn off NVIDIA dgpu when in integrated mode
|
||||
# without needing to restart the computer. This is needed because computers like the Asus G14
|
||||
# will wake after suspend in Hybrid mode, even if the system was in Integrated mode before
|
||||
# suspending.
|
||||
|
||||
restore_marker=/run/omarchy-force-igpu-integrated
|
||||
sleep_action=${SYSTEMD_SLEEP_ACTION:-$2}
|
||||
[[ -x /usr/bin/supergfxctl ]] || exit 0
|
||||
|
||||
switch_mode() {
|
||||
local expected="$1" current
|
||||
|
||||
if ! /usr/bin/timeout --kill-after=1s 3s /usr/bin/supergfxctl -m "$expected"; then
|
||||
echo "Could not request the GPU transition to $expected mode" >&2
|
||||
return 1
|
||||
fi
|
||||
for _ in {1..10}; do
|
||||
if current=$(/usr/bin/timeout --kill-after=1s 2s /usr/bin/supergfxctl -g 2>/dev/null) &&
|
||||
[[ $current == "$expected" ]]; then
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "Could not confirm the GPU transition to $expected mode" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
pre)
|
||||
# Remember the mode this sleep cycle started in. supergfxctl persists the
|
||||
# temporary hibernate switch to Vfio, so post must not consult that mutable
|
||||
# value when deciding whether to restore Integrated mode.
|
||||
if [[ -L $restore_marker ]]; then
|
||||
exit 1
|
||||
elif [[ ! -f $restore_marker ]]; then
|
||||
/usr/bin/grep -Eq '"mode"[[:space:]]*:[[:space:]]*"Integrated"' /etc/supergfxd.conf 2>/dev/null || exit 0
|
||||
/usr/bin/install -m 0600 -o root -g root -T /dev/null "$restore_marker"
|
||||
fi
|
||||
|
||||
# Before hibernating, switch to Vfio so the nvidia driver is detached from the dGPU.
|
||||
# Without this, hibernate resume fails because the nvidia driver can't freeze a
|
||||
# powered-off dGPU (returns -EIO), which aborts the entire resume.
|
||||
if [[ $2 == "hibernate" ]]; then
|
||||
/usr/bin/supergfxctl -m Vfio
|
||||
sleep 1
|
||||
if [[ $sleep_action == "hibernate" ]]; then
|
||||
switch_mode Vfio
|
||||
fi
|
||||
;;
|
||||
post)
|
||||
[[ -f $restore_marker && ! -L $restore_marker ]] || exit 0
|
||||
|
||||
# small delay so the device is fully re-enumerated
|
||||
sleep 4
|
||||
|
||||
# force-bind dGPU to vfio (fully detached from nvidia)
|
||||
/usr/bin/supergfxctl -m Vfio
|
||||
sleep 1
|
||||
switch_mode Vfio
|
||||
|
||||
# then go back to Integrated, which powers it off again
|
||||
/usr/bin/supergfxctl -m Integrated
|
||||
switch_mode Integrated
|
||||
/usr/bin/rm -f -- "$restore_marker"
|
||||
;;
|
||||
esac
|
||||
@@ -3,7 +3,9 @@
|
||||
# Turn off keyboard backlight before hibernate to prevent hang on power-off.
|
||||
# The ASUS keyboard controller can block S4 shutdown if LEDs are active.
|
||||
|
||||
if [[ $1 == "pre" && $2 == "hibernate" ]]; then
|
||||
sleep_action=${SYSTEMD_SLEEP_ACTION:-$2}
|
||||
|
||||
if [[ $1 == "pre" && $sleep_action == "hibernate" ]]; then
|
||||
device=""
|
||||
for candidate in /sys/class/leds/*kbd_backlight*; do
|
||||
if [[ -e "$candidate" ]]; then
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
[Service]
|
||||
ExecStart=
|
||||
ExecStart=/usr/bin/updatedb --prune-bind-mounts=no --add-prunepaths=/.snapshots
|
||||
@@ -1,6 +1,6 @@
|
||||
[Unit]
|
||||
Description=Omarchy speaker tuning filter-chain
|
||||
Documentation=https://github.com/basecamp/omarchy/blob/master/docs/audio-tuning.md
|
||||
Documentation=https://github.com/basecamp/omarchy/blob/master/docs/AUDIO-TUNING.md
|
||||
# WirePlumber does the linking, so starting before it is up risks the output being
|
||||
# linked before the speaker device has been discovered.
|
||||
After=pipewire.service wireplumber.service
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
name: omarchy
|
||||
description: Omarchy system theme
|
||||
colors:
|
||||
background: "{{ background }}"
|
||||
ui_text: "{{ foreground }}"
|
||||
ui_primary: "{{ accent }}"
|
||||
ui_accent: "{{ accent }}"
|
||||
ui_border: "{{ muted }}"
|
||||
ui_label: "{{ accent }}"
|
||||
ui_ok: "{{ green }}"
|
||||
ui_warn: "{{ yellow }}"
|
||||
ui_error: "{{ red }}"
|
||||
ui_tool: "{{ cyan }}"
|
||||
ui_thinking: "{{ dark_foreground }}"
|
||||
banner_border: "{{ muted }}"
|
||||
banner_title: "{{ accent }}"
|
||||
banner_accent: "{{ accent }}"
|
||||
banner_dim: "{{ dark_foreground }}"
|
||||
banner_text: "{{ foreground }}"
|
||||
prompt: "{{ bright_foreground }}"
|
||||
input_rule: "{{ muted }}"
|
||||
response_border: "{{ accent }}"
|
||||
shell_dollar: "{{ blue }}"
|
||||
selection_bg: "{{ selection }}"
|
||||
session_label: "{{ accent }}"
|
||||
session_border: "{{ muted }}"
|
||||
status_bar_bg: "{{ dark_background }}"
|
||||
status_bar_text: "{{ foreground }}"
|
||||
status_bar_strong: "{{ accent }}"
|
||||
status_bar_dim: "{{ dark_foreground }}"
|
||||
status_bar_good: "{{ green }}"
|
||||
status_bar_warn: "{{ yellow }}"
|
||||
status_bar_bad: "{{ red }}"
|
||||
status_bar_critical: "{{ bright_red }}"
|
||||
voice_status_bg: "{{ dark_background }}"
|
||||
completion_menu_bg: "{{ lighter_background }}"
|
||||
completion_menu_current_bg: "{{ selection }}"
|
||||
completion_menu_meta_bg: "{{ lighter_background }}"
|
||||
completion_menu_meta_current_bg: "{{ selection }}"
|
||||
diff_added: "{{ mix background green 15% }}"
|
||||
diff_removed: "{{ mix background red 15% }}"
|
||||
diff_added_word: "{{ green }}"
|
||||
diff_removed_word: "{{ red }}"
|
||||
syntax_string: "{{ green }}"
|
||||
syntax_number: "{{ yellow }}"
|
||||
syntax_keyword: "{{ magenta }}"
|
||||
syntax_comment: "{{ muted }}"
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "Omarchy",
|
||||
"appearance": "{{ mode }}",
|
||||
"canvas": "{{ background }}",
|
||||
"accent": "{{ accent }}",
|
||||
"colors": {
|
||||
"chrome": "{{ background }}",
|
||||
"toolbar": "{{ background }}",
|
||||
"toolbarForeground": "{{ foreground }}",
|
||||
"toolbarBorder": "{{ muted }}",
|
||||
|
||||
"text": "{{ foreground }}",
|
||||
"border": "{{ muted }}",
|
||||
"focus": "{{ accent }}",
|
||||
|
||||
"sidebar": "{{ dark_background }}",
|
||||
"sidebarForeground": "{{ foreground }}",
|
||||
"sidebarBorder": "{{ muted }}",
|
||||
"sidebarRowHover": "{{ mix dark_background foreground 6% }}",
|
||||
"sidebarRowActive": "{{ mix dark_background accent 18% }}",
|
||||
"sidebarRowSelected": "{{ selection }}",
|
||||
|
||||
"error": "{{ mix red foreground 35% }}",
|
||||
"warning": "{{ mix yellow foreground 35% }}",
|
||||
|
||||
"codeBackground": "{{ dark_background }}",
|
||||
"codeForeground": "{{ foreground }}",
|
||||
|
||||
"terminalBackground": "{{ background }}",
|
||||
"terminalForeground": "{{ foreground }}",
|
||||
"terminalCursor": "{{ bright_foreground }}",
|
||||
"terminalSelection": "{{ selection }}",
|
||||
"terminalScrollbar": "{{ muted }}",
|
||||
"terminalScrollbarHover": "{{ dark_foreground }}"
|
||||
}
|
||||
}
|
||||
@@ -12,9 +12,7 @@ default/audio/tunings/<vendor>-<model>/
|
||||
```
|
||||
|
||||
`on` renders the graph into `~/.config/pipewire/omarchy-speaker-tuning.conf.d/`
|
||||
(plus the shared host config `omarchy-speaker-tuning.conf`, from
|
||||
`default/audio/filter-chain-host.conf`) and runs it as its own PipeWire client via
|
||||
`omarchy-speaker-tuning.service`, rather than
|
||||
and runs it as its own PipeWire client via `omarchy-speaker-tuning.service`, rather than
|
||||
loading it into the audio daemon. The daemon only reads its own config at startup,
|
||||
so a daemon-loaded tuning could only be switched by restarting PipeWire — which
|
||||
drops every PulseAudio client's connection, and applications that do not reconnect
|
||||
@@ -42,13 +40,6 @@ omarchy audio tuning status # installed? in use? what matches?
|
||||
`match` and `fronted-sink` are also accepted; they exist for the install hooks
|
||||
and the sink-listing scripts rather than for daily use.
|
||||
|
||||
`on` verifies its own work: it waits for the tuning sink to appear and confirms
|
||||
its output linked to the expected physical sink, and uninstalls everything again
|
||||
on either failure — so a broken tuning cannot be left half-applied. It also
|
||||
switches the default sink to the tuning and moves existing app streams onto it
|
||||
(`off` reverses both). When everything is already installed and linked, `on` is
|
||||
a no-op; pass `--force` when iterating on a tuning in place.
|
||||
|
||||
## Adding a tuning
|
||||
|
||||
Add a directory with a `tuning.conf` and a `filter-chain.conf`. No new command is
|
||||
@@ -68,16 +59,8 @@ been validated on:
|
||||
match_sku=("0DB9" "0DBA") # XPS 14 and XPS 16
|
||||
```
|
||||
|
||||
Only the first *defined* key is consulted, in the order `match_command`,
|
||||
`match_sku`, `match_dmi` — keys below a defined one are ignored, and a tuning
|
||||
defining none never matches.
|
||||
|
||||
`sink_pattern` is required whichever method you use, since the graph's target sink
|
||||
is substituted from it. The graph must also keep two fixed properties: the sink
|
||||
node is named `omarchy_speaker_tuning` (the install checks for that exact name,
|
||||
so renaming it silently fails verification), and its output stream sets
|
||||
`node.dont-fallback` alongside `node.dont-move`, so WirePlumber cannot link it
|
||||
somewhere else when the target sink is absent at host start.
|
||||
is substituted from it.
|
||||
|
||||
Gate narrowly and widen as models are validated; a tuning aimed at the wrong
|
||||
drivers can sound worse than none and can stress them. When a tuning covers a model
|
||||
@@ -1,130 +0,0 @@
|
||||
# The Omarchy CLI router
|
||||
|
||||
`bin/omarchy` maps spaced commands onto the flat `bin/omarchy-*` namespace:
|
||||
`omarchy theme set foo` becomes `exec bin/omarchy-theme-set foo`. There is no
|
||||
registry to maintain — every executable `bin/omarchy-*` file is a command, and
|
||||
its filename is its default route. Metadata comments in the file header refine
|
||||
how it presents and routes; the keys are documented in
|
||||
[`agents/skills/command-metadata.md`](../agents/skills/command-metadata.md).
|
||||
This document covers what that guide does not: how resolution and dispatch
|
||||
actually work.
|
||||
|
||||
## How a binary becomes routes
|
||||
|
||||
The stem after `omarchy-` splits at the first hyphen: `omarchy-theme-set` gets
|
||||
group `theme` and name `set`, with remaining hyphens becoming spaces
|
||||
(`omarchy-hw-asus-rog` → group `hw`, name `asus rog`). A single-segment stem
|
||||
(`omarchy-update`) is the root command of its own group, with an empty name.
|
||||
|
||||
Every command registers two routes: the canonical route `omarchy <group>
|
||||
<name>` after metadata overrides, and the filename route with *all* hyphens
|
||||
turned to spaces. When metadata moves nothing, they are the same route. When it
|
||||
does, both keep working — `# omarchy:name=gaming xbox-cloud` on
|
||||
`omarchy-install-gaming-xbox-cloud` keeps the hyphen inside the name, so
|
||||
`omarchy install gaming xbox-cloud` is canonical while the filename route
|
||||
`omarchy install gaming xbox cloud` still resolves. An explicitly *empty* `#
|
||||
omarchy:name=` makes a command the root of its group: `omarchy-menu-share` sets
|
||||
`group=share` and an empty name, so its canonical route is `omarchy share`
|
||||
while `omarchy menu share` remains as the filename route. Alias routes register
|
||||
the same way but are flagged, so listings show them as aliases rather than
|
||||
commands.
|
||||
|
||||
Two routes claiming different binaries is a collision: the first registration
|
||||
wins, dispatch is unaffected, and the conflict is recorded for `omarchy
|
||||
commands --check` to report. Hidden commands (`# omarchy:hidden=true`) still
|
||||
register and dispatch normally — hiding only removes them from listings, which
|
||||
is how install-time plumbing like `omarchy apply hardware` stays callable
|
||||
without being browsable.
|
||||
|
||||
Metadata is read from the comment header only: the first 80 lines, stopping at
|
||||
the first non-comment line, so metadata-shaped comments after code never take
|
||||
effect. Malformed `omarchy:` lines and unknown keys are ignored rather than
|
||||
fatal — a typo degrades a command to its filename route instead of breaking the
|
||||
router. The first plain comment line doubles as a fallback summary, and a
|
||||
command with no comments at all still gets a generated one, though `--check`
|
||||
demands the explicit form (below).
|
||||
|
||||
## Dispatch
|
||||
|
||||
Resolution is longest-prefix: the router tries the full argument list as a
|
||||
route, then drops trailing words until something matches. Whatever it drops is
|
||||
passed to the binary as arguments. This runs in two passes.
|
||||
|
||||
The fast path joins argument prefixes with hyphens and checks for an
|
||||
executable file: `omarchy theme set foo` probes `omarchy-theme-set-foo`, then
|
||||
`omarchy-theme-set`, which exists — resolved without reading a single metadata
|
||||
header. This exists because plain dispatch is the hot path: parsing the
|
||||
headers of several hundred binaries on every invocation is measurable
|
||||
latency (`omarchy dev benchmark cli` tracks it), and a filename probe is a few
|
||||
stat calls. Metadata loads lazily, only for the resolved command when help is
|
||||
needed.
|
||||
|
||||
When no filename matches — metadata-moved routes like `omarchy share`, and
|
||||
aliases like `omarchy screenshot` — the router falls back to loading all
|
||||
metadata and resolving against the registered route table, with the same
|
||||
longest-prefix rule.
|
||||
|
||||
Both paths intercept `--help`/`-h` *anywhere* in the leftover arguments, not
|
||||
just the first one. Resolution can succeed with unresolved words still ahead of
|
||||
the flag — `omarchy update aur --help` resolves `update` with leftovers `aur
|
||||
--help` — and checking only the first leftover once let that invocation start a
|
||||
real update. A `--` ends the scan: everything after it belongs to the command,
|
||||
so `omarchy foo run -- --help` forwards the flag. `--json` alongside `--help`
|
||||
switches the help output to the command's JSON record; `--json` alone is just
|
||||
an argument for the command.
|
||||
|
||||
Bare invocations are also guarded. If a command declares required arguments
|
||||
(its `args` metadata, minus `[bracketed]` optional parts, is non-empty) and
|
||||
none were given, the router shows help instead of executing — `omarchy theme
|
||||
set` prints usage rather than running an interactive setter. A bare group name
|
||||
with child commands shows the group help.
|
||||
|
||||
Dispatch is `exec`: the router process is replaced, the binary sees only the
|
||||
leftover arguments, and the exit code is the binary's own. The router itself
|
||||
exits 127 for unknown routes or missing binaries.
|
||||
|
||||
When nothing resolves, the router tries a prefix listing — `omarchy hw asus`
|
||||
prints every command whose usage starts with that prefix — and otherwise
|
||||
errors with a "did you mean" suggestion (a known route extending the first
|
||||
word) and a pointer to `omarchy commands --all`.
|
||||
|
||||
## Groups and the top-level listing
|
||||
|
||||
Group help is synthesized from metadata, not written anywhere. A command
|
||||
belongs to a group when either its metadata group or its filename group
|
||||
matches, listed under the route that fits the group being viewed: `omarchy
|
||||
menu --help` shows `omarchy-menu-share` as `omarchy menu share`, while its
|
||||
canonical `omarchy share` stands alone. On the fast path, group help loads
|
||||
only that group's filename-prefixed binaries rather than everything.
|
||||
|
||||
The top-level `omarchy` listing is driven entirely by the hand-curated
|
||||
`GROUP_DESCRIPTIONS` table in `bin/omarchy`, which also titles each group's
|
||||
help. An entry there advertises the group even when every command in it is
|
||||
hidden — which is exactly why `apply` and `provision` have none: they route,
|
||||
but a listing entry would put install-time plumbing back in front of users
|
||||
(see the Command Naming section of `AGENTS.md`). Adding a browsable command
|
||||
group means adding its `GROUP_DESCRIPTIONS` entry; adding hidden plumbing
|
||||
means deliberately not doing so.
|
||||
|
||||
## Introspection
|
||||
|
||||
`omarchy commands` prints every non-hidden command with its summary, plus an
|
||||
alias table. `--all` includes hidden commands, `--markdown` emits a table, and
|
||||
`--json` emits full records: route, binary, group, name, summary, flags, args,
|
||||
examples, aliases, `filename_route`, and `routes` (the union of everything
|
||||
that resolves to the binary). Per-command JSON comes from `omarchy <route>
|
||||
--help --json`.
|
||||
|
||||
`omarchy commands --check` is the metadata lint, run by `test/cli`. It fails
|
||||
on:
|
||||
|
||||
- route collisions between binaries
|
||||
- a missing explicit `# omarchy:summary=` — a plain-comment fallback renders
|
||||
in help but does not satisfy the check
|
||||
- invalid boolean metadata: `hidden` and `requires-sudo` must be `true` or
|
||||
omitted, never `false`
|
||||
- a registered command whose binary is missing or not executable
|
||||
|
||||
When debugging a routing surprise, `omarchy <route> --help` shows the resolved
|
||||
binary and, when it differs, the filename route; `omarchy commands --all
|
||||
--json` shows every route the router knows.
|
||||
+62
-81
@@ -5,8 +5,8 @@ system.
|
||||
|
||||
## Mental model
|
||||
|
||||
Two Arch packages are built from this one repo (PKGBUILDs live in the
|
||||
separate `omarchy-pkgs` repository, under `pkgbuilds/`):
|
||||
Two Arch packages are built from this one repo (PKGBUILDs live in
|
||||
`omarchy-pkgs/pkgbuilds/`):
|
||||
|
||||
- **`omarchy`** — runtime binaries (`bin/`, including `bin/omarchy-dev-*`),
|
||||
install/finalize scripts (`install/`), migrations, themes, and the
|
||||
@@ -22,24 +22,19 @@ separate `omarchy-pkgs` repository, under `pkgbuilds/`):
|
||||
(`omarchy-debug`, `omarchy-debug-idle`, `omarchy-upload-log`) needed by
|
||||
the live ISO env.
|
||||
|
||||
Two other packages live in `omarchy-pkgs` but stand alone:
|
||||
Two other packages live in `omarchy-pkgs/` but stand alone:
|
||||
`omarchy-keyring` (GPG keys for pacman) and `omarchy-nvim` (the Neovim
|
||||
setup; independently seeds `/etc/skel`).
|
||||
|
||||
Some trees ship in neither package and exist only in the repo: `manual/`
|
||||
(user manual chapters), `agents/skills/` (contributor task guides), `docs/`,
|
||||
`test/`, and `plans/`.
|
||||
|
||||
Three layers populate `$HOME`:
|
||||
|
||||
1. **Seed** — `omarchy-settings` ships static defaults to `/etc/skel/`.
|
||||
Arch's `useradd -m` copies that tree into a new user's `$HOME` at user
|
||||
creation. This is the only mechanism that touches a brand-new user's home
|
||||
for these files.
|
||||
2. **Finalize** — `omarchy-provision-user` (routed as `omarchy finalize
|
||||
user`) runs once per user and handles the things `/etc/skel` can't do
|
||||
because they need `$HOME` expansion, the live `$OMARCHY_PATH`, or runtime
|
||||
detection of system state.
|
||||
2. **Finalize** — `omarchy-finalize-user` runs once per user and handles the
|
||||
things `/etc/skel` can't do because they need `$HOME` expansion, the live
|
||||
`$OMARCHY_PATH`, or runtime detection of system state.
|
||||
3. **Resync** — `omarchy-reinstall-configs` is the explicit, destructive
|
||||
command for an existing user to clobber their configs back to shipped
|
||||
defaults.
|
||||
@@ -47,13 +42,6 @@ Three layers populate `$HOME`:
|
||||
`/etc/skel` only fires at user creation. Existing users picking up new
|
||||
defaults must use the resync command.
|
||||
|
||||
Deferred-provisioning installs (`omarchy-apply-system --defer-provisioning`)
|
||||
create no user at all: the ISO leaves `/var/lib/omarchy/provisioning/pending`
|
||||
behind, which arms `omarchy-provision-owner.service` (shipped from
|
||||
`install/provisioning/`, alongside the factory-reset finish unit and
|
||||
`setup-form.sh`). On first boot `bin/omarchy-provision-owner` creates the
|
||||
user on tty1 and runs the finalize step itself.
|
||||
|
||||
Current generated theme state lives under
|
||||
`~/.local/state/omarchy/current/`. Keep `~/.config/omarchy/` for files a user
|
||||
may intentionally version in a dotfile manager, such as user themes, hooks,
|
||||
@@ -84,6 +72,7 @@ version ──► omarchy /usr/share/omarchy
|
||||
config/** ──► omarchy-settings /etc/skel/.config/** (seeds new users)
|
||||
/usr/share/omarchy/config/** (resync source)
|
||||
etc/fastfetch/config.jsonc ──► omarchy-settings /etc/fastfetch/config.jsonc
|
||||
etc/xdg/kitty/kitty.conf ──► omarchy-settings /etc/xdg/kitty/kitty.conf
|
||||
|
||||
applications/*.desktop ──► omarchy-settings /etc/skel/.local/share/applications/
|
||||
/usr/share/omarchy/applications/
|
||||
@@ -94,12 +83,7 @@ applications/icons/* ──► omarchy-settings /usr/share/icons/h
|
||||
|
||||
etc/** ──► omarchy-settings /etc/** (drop-ins we own outright)
|
||||
├─ mkinitcpio.conf.d/{omarchy_hooks,thunderbolt_module}.conf
|
||||
├─ limine-entry-tool.d/{omarchy-defaults,omarchy-uki}.conf
|
||||
├─ NetworkManager/, sudoers.d/, sysctl.d/, tmpfiles.d/,
|
||||
│ profile.d/omarchy.sh, … (a summary — `ls etc/` for the full ~17-entry tree)
|
||||
└─ security/faillock.conf, nsswitch.conf,
|
||||
cups/cups-browsed.conf, plymouth/plymouthd.conf /usr/share/omarchy/etc-overrides/
|
||||
→ /etc/* (post_install cp -f, see below)
|
||||
└─ limine-entry-tool.d/{omarchy-defaults,omarchy-uki}.conf
|
||||
|
||||
default/limine/limine.conf ──► omarchy-settings /usr/share/omarchy/default/limine/limine.conf
|
||||
default/limine/default.conf ──► omarchy-settings /usr/share/omarchy/default/limine/default.conf
|
||||
@@ -112,8 +96,7 @@ default/** ──► omarchy-settings /usr/share/omarchy
|
||||
│ (sourced by every shell/session entry point; see "Env bootstrap")
|
||||
├─ bashrc /usr/share/omarchy/etc-overrides/dot.bashrc
|
||||
│ → /etc/skel/.bashrc (post_install cp -f)
|
||||
├─ hypr/toggles/*.lua (flags,
|
||||
│ single-window-aspect-ratio, window-no-gaps) /etc/skel/.local/state/omarchy/toggles/hypr/
|
||||
├─ hypr/toggles/flags.lua /etc/skel/.local/state/omarchy/toggles/hypr/
|
||||
├─ nautilus-python/extensions/*.py /etc/skel/.local/share/nautilus-python/extensions/
|
||||
├─ tensaku/state.toml /etc/skel/.local/state/tensaku/state.toml
|
||||
├─ uwsm/env.d/10-omarchy /usr/share/uwsm/env.d/
|
||||
@@ -122,16 +105,18 @@ default/** ──► omarchy-settings /usr/share/omarchy
|
||||
│ + symlink /etc/fonts/conf.d/50-omarchy.conf
|
||||
├─ xdg-terminal-exec/*.list /usr/share/xdg-terminal-exec/
|
||||
├─ applications/mimeapps.list /usr/share/applications/mimeapps.list
|
||||
├─ systemd/user/*.service /usr/lib/systemd/user/
|
||||
├─ systemd/user/*.{service,path} /usr/lib/systemd/user/
|
||||
├─ systemd/user/app.slice.d/10-oomd.conf /usr/lib/systemd/user/app.slice.d/
|
||||
├─ systemd/system-sleep/{force-igpu,
|
||||
│ keyboard-backlight,unmount-fuse} /usr/lib/systemd/system-sleep/
|
||||
├─ systemd/system-sleep/unmount-fuse /usr/lib/systemd/system-sleep/
|
||||
├─ systemd/zram-generator.conf.d/90-omarchy.conf /usr/lib/systemd/zram-generator.conf.d/
|
||||
├─ fonts/omarchy/omarchy.ttf /usr/share/fonts/omarchy/
|
||||
├─ sddm/omarchy/ /usr/share/sddm/themes/omarchy/
|
||||
├─ sddm/hyprland.lua /usr/share/sddm/hyprland.lua
|
||||
├─ wayland-sessions/omarchy.desktop /usr/local/share/wayland-sessions/
|
||||
└─ plymouth/ /usr/share/plymouth/themes/omarchy/
|
||||
├─ plymouth/ /usr/share/plymouth/themes/omarchy/
|
||||
└─ security/faillock, nsswitch, cups-browsed,
|
||||
plymouthd.conf, os-release /usr/share/omarchy/etc-overrides/
|
||||
→ /etc/* (post_install cp -f, see below)
|
||||
|
||||
logo.{txt,svg}, icon.{txt,png} ──► omarchy-settings /usr/share/omarchy/ (resync source)
|
||||
/usr/share/pixmaps/omarchy.png
|
||||
@@ -139,19 +124,28 @@ logo.{txt,svg}, icon.{txt,png} ──► omarchy-settings /usr/share/omarchy
|
||||
/etc/skel/.config/omarchy/branding/{about,screensaver}.txt
|
||||
```
|
||||
|
||||
The hardware-conditional `force-igpu` and `keyboard-backlight` sources also live under `default/systemd/system-sleep/`, but their setup commands publish root-owned copies only on machines that need them; they are not installed by `omarchy-settings`.
|
||||
|
||||
### Why `etc-overrides/` exists
|
||||
|
||||
Some files under `/etc/` (`.bashrc` in `/etc/skel`, `nsswitch.conf`,
|
||||
`security/faillock.conf`, `cups/cups-browsed.conf`, `plymouth/plymouthd.conf`)
|
||||
are owned by upstream Arch packages, so we can't install over them via pacman
|
||||
without a file conflict. Instead their sources (under `etc/` in the repo;
|
||||
`.bashrc` from `default/bashrc`) ship at
|
||||
`security/faillock.conf`, `cups/cups-browsed.conf`, `plymouth/plymouthd.conf`,
|
||||
`os-release`) are owned by upstream Arch packages, so we can't install over
|
||||
them via pacman without a file conflict. Instead they ship at
|
||||
`/usr/share/omarchy/etc-overrides/` and the `omarchy-settings` `post_install`
|
||||
/ `post_upgrade` scriptlet `cp -f`'s them into place.
|
||||
|
||||
Tradeoff: user edits to those files get clobbered on every `omarchy-settings`
|
||||
upgrade. This is documented in the PKGBUILD.
|
||||
|
||||
## Locate indexing
|
||||
|
||||
`default/systemd/system/plocate-updatedb.service.d/10-omarchy.conf` ships through `omarchy-settings` to `/usr/lib/systemd/system/plocate-updatedb.service.d/10-omarchy.conf`. It replaces the existing service's `ExecStart` with `updatedb --prune-bind-mounts=no --add-prunepaths=/.snapshots`, keeping Btrfs subvolume mounts searchable and excluding Snapper snapshots. The upstream service retains its timer, resource limits, and sandbox; Omarchy's existing AC-power condition still applies.
|
||||
|
||||
`/etc/updatedb.conf` remains owned by plocate and is never rewritten by Omarchy. The command-line options override bind-mount pruning and add to the administrator's existing path exclusions. Installer and AUR package refreshes pass the same options directly because installation may run without systemd and an explicitly requested refresh should work on battery.
|
||||
|
||||
Arch's systemd package hook reloads units when the vendor drop-in is installed or upgraded. The settings package containing the drop-in must ship alongside the runtime package that removes the old configuration helper and migration. Pacman removes those retired files; no new state migration is needed. A running indexer finishes with its original options, and subsequent service starts use the drop-in. For an immediate local test after installing the packages, restart `plocate-updatedb.service` while connected to AC power.
|
||||
|
||||
## Env bootstrap (`default/bash/env-bootstrap`)
|
||||
|
||||
Single source of truth for `OMARCHY_PATH` and dev-link-aware `PATH`. It:
|
||||
@@ -163,10 +157,6 @@ Single source of truth for `OMARCHY_PATH` and dev-link-aware `PATH`. It:
|
||||
- Prepends `$OMARCHY_PATH/bin` to `PATH` **only when** `OMARCHY_PATH` is
|
||||
not `/usr/share/omarchy`. On a production install the binaries are
|
||||
already on `PATH` as `/usr/bin/omarchy-*` via the `omarchy` package.
|
||||
- Appends `~/.local/share/mise/shims` and `~/.local/bin` so login shells and
|
||||
the uwsm session find mise-managed tools — kept in sync with the PAM `PATH`
|
||||
line written by `install/config/ssh-command-path.sh`, which covers SSH
|
||||
commands that run no shell setup at all.
|
||||
|
||||
Sourced by every entry point that needs the env set:
|
||||
|
||||
@@ -192,17 +182,13 @@ yet and silently runs the packaged copy of one it has. The drop-in is validated
|
||||
with `visudo -c` before install and removed by `omarchy-dev-unlink`; unlike
|
||||
`/etc/omarchy.conf`, it takes effect without a reboot.
|
||||
|
||||
## Runtime finalization (`omarchy-provision-user`)
|
||||
## Runtime finalization (`omarchy-finalize-user`)
|
||||
|
||||
Runs once per user. It does **not** copy `~/.config/**`, `~/.bashrc`,
|
||||
`flags.lua`, or the nautilus extensions — `/etc/skel` already seeded those.
|
||||
It only does the things `/etc/skel` can't:
|
||||
|
||||
- Skill symlinks `~/.{agents,claude,codex,pi/agent}/skills/<name>` →
|
||||
`$OMARCHY_PATH/default/agents/skills/<name>`, looping over every skill
|
||||
directory there (currently `omarchy` and `diagnose-crash`) so new skills
|
||||
need no edit. Symlinks (not copies) so `omarchy dev link` against a dev
|
||||
checkout repoints them correctly.
|
||||
- Skill symlinks into `~/.agents/skills/<name>`, `~/.claude/skills/<name>`, `~/.codex/skills/<name>`, `~/.pi/agent/skills/<name>`, `~/.hermes/skills/<name>`, and each existing `~/.hermes/profiles/*/skills/<name>` → `$OMARCHY_PATH/default/agents/skills/<name>`, looping over every skill directory there (currently `omarchy` and `diagnose-crash`) so new skills need no edit. Symlinks (not copies) so `omarchy dev link` against a dev checkout repoints them correctly. Hermes profile dirs are only linked when they already exist — provision does not create Hermes profiles.
|
||||
- `xdg-user-dirs-update` (Templates/Public/Desktop folded back into `$HOME`)
|
||||
and `~/.config/gtk-3.0/bookmarks` (needs `$HOME` expansion).
|
||||
- Hyprland's package-owned default input reads `XKBLAYOUT` / `XKBVARIANT`
|
||||
@@ -210,23 +196,21 @@ It only does the things `/etc/skel` can't:
|
||||
- `xdg-settings set default-web-browser chromium.desktop` and
|
||||
`xdg-mime default HEY.desktop x-scheme-handler/mailto` (XDG-aware paths).
|
||||
- `omarchy-refresh-applications` (composes generated `.desktop` launchers).
|
||||
- Sources `install/user/all.sh` — theme, chromium, git, xcompose, mise,
|
||||
keyring, per-user hardware quirks (asus mic/mixer, framework f13 audio, …).
|
||||
- Sources `install/user/all.sh` — theme, git, mise, keyring, per-user
|
||||
hardware quirks (asus mic/mixer, framework f13 audio, …).
|
||||
- On `--first-install`, marks every shipped user migration as already applied
|
||||
for the freshly-created user.
|
||||
|
||||
Idempotency marker: `~/.local/state/omarchy/done/finalize-user`, managed
|
||||
by `omarchy-done`.
|
||||
|
||||
The ISO calls it as `omarchy-provision-user --force --first-install` in the
|
||||
The ISO calls it as `omarchy-finalize-user --force --first-install` in the
|
||||
target chroot as the install user, after `omarchy-apply-system` has finished
|
||||
the root-side work. `omarchy-provision-owner` makes the same call (with
|
||||
`OMARCHY_SETUP_CONTEXT=provision-owner`) when it creates the user during
|
||||
deferred first-boot provisioning.
|
||||
the root-side work.
|
||||
|
||||
## Migrations (`omarchy-migrate`)
|
||||
|
||||
See [`migrations.md`](../agents/skills/migrations.md) for the full migration model, authoring
|
||||
See [`migrations.md`](migrations.md) for the full migration model, authoring
|
||||
guidelines, and troubleshooting notes.
|
||||
|
||||
Omarchy migrations live in `migrations/*.sh` and run per-user through
|
||||
@@ -238,10 +222,9 @@ machine-wide repairs should no-op when another user already applied them.
|
||||
|
||||
Each graphical user has `omarchy-migrate-notify.service`, started once per login
|
||||
through `WantedBy=graphical-session.target` and ordered after that target so
|
||||
notification actions can safely launch through UWSM. The `omarchy-pkgs`
|
||||
PKGBUILD has shipped `omarchy-update-user-notify.service` as a symlink onto
|
||||
it, so users enabled under the old unit name keep working before they reach
|
||||
migration `1785095882`.
|
||||
notification actions can safely launch through UWSM. The package also ships
|
||||
`omarchy-update-user-notify.service` as a symlink onto it, so users enabled
|
||||
under the old unit name keep working before they reach migration `1785095882`.
|
||||
It runs `omarchy-migrate-notify` as
|
||||
that user, which checks `omarchy-migrate --pending`. If this user has missing
|
||||
migration state, it shows a notification that opens a terminal for
|
||||
@@ -260,31 +243,27 @@ transaction in the already-visible update terminal, then runs
|
||||
|
||||
## First-run (`omarchy-provision-first-run`)
|
||||
|
||||
Runs once on first interactive login, after the user manager is live. It
|
||||
first runs `omarchy-provision-user || true` so finalize catches up if it
|
||||
never ran, then handles the steps that need a running graphical session
|
||||
and/or a working user systemd instance:
|
||||
Runs once on first interactive login, after the user manager is live. Used
|
||||
for steps that need a running graphical session and/or a working user
|
||||
systemd instance:
|
||||
|
||||
- `omarchy-hook-install post-update` for the three shipped hooks
|
||||
(`install-voxtype.hook`, `setup-fingerprint.hook`, `setup-agent.hook`).
|
||||
- `install/user/first-run/enable-user-units.sh` — daemon-reload, then
|
||||
`systemctl --user enable --now` the shipped user units (`bt-agent`,
|
||||
`omarchy-sleep-lock`, `omarchy-recover-internal-monitor`,
|
||||
`omarchy-migrate-notify.service`, `omarchy-fcitx5.service`,
|
||||
`omarchy-crash-watch.service`) so they run in the first session too.
|
||||
- `omarchy-hook-install post-update install-voxtype.hook` — register the
|
||||
Voxtype post-update hook.
|
||||
- `install/user/first-run/enable-user-units.sh` — `systemctl --user enable`
|
||||
the shipped user units (`bt-agent`, `omarchy-sleep-lock`,
|
||||
`omarchy-recover-internal-monitor`, `omarchy-migrate-notify.service`,
|
||||
`omarchy-fcitx5.service`).
|
||||
Done here, not at finalize, because
|
||||
the user manager isn't reachable from the ISO chroot; `ConditionPath*`
|
||||
in the unit files keeps services inert when they don't apply.
|
||||
- `install/user/first-run/gnome-theme.sh`,
|
||||
`install/user/first-run/gtk-primary-paste.sh` — GNOME/GTK settings that
|
||||
need the dconf daemon.
|
||||
- `install/user/first-run/audio-tuning.sh` — apply speaker tuning.
|
||||
- `install/user/first-run/welcome.sh` — keybindings toast that greets the
|
||||
first login and opens the cheatsheet when clicked. The caller runs
|
||||
`omarchy-notification-wait` once before this and the Wi-Fi step, so both
|
||||
toasts land on a live notification server.
|
||||
- `install/user/first-run/wifi.sh` — Wi-Fi/update toasts (waits detached on
|
||||
`nm-online` so the update prompt only lands once there is a connection).
|
||||
first login and opens the cheatsheet when clicked.
|
||||
- `install/user/first-run/wifi.sh` — Wi-Fi/update toasts (waits for a live
|
||||
notification server before firing, then waits detached on `nm-online` so the
|
||||
update prompt only lands once there is a connection).
|
||||
|
||||
The entire sequence has one idempotency marker:
|
||||
`~/.local/state/omarchy/done/first-run-user`, managed by `omarchy-done`.
|
||||
@@ -304,8 +283,8 @@ the legacy finalization marker from `~/.local/state/omarchy/` into `done/`.
|
||||
finalization. It sources:
|
||||
|
||||
- `install/config/all.sh` — theme links, lockout limits, lockscreen PAM,
|
||||
powerprofilesctl shebang fix, SSH command path and keepalive, docker setup,
|
||||
Snapper retention, locate index tuning, service enablement, firewall.
|
||||
powerprofilesctl shebang fix, docker setup, Snapper retention, locate
|
||||
index tuning, service enablement, firewall.
|
||||
- `install/hardware/all.sh` via `omarchy-apply-hardware` — vendor- and
|
||||
device-specific kernel modules, udev rules, microcode, wireless regdom,
|
||||
ASUS / Framework / Intel / Apple / Lenovo quirks.
|
||||
@@ -315,10 +294,6 @@ finalization. It sources:
|
||||
Logging goes to `/var/log/omarchy-install.log` via
|
||||
`install/helpers/logging.sh`.
|
||||
|
||||
The package lists the ISO pacstraps live at `install/omarchy-base.packages`
|
||||
and `install/omarchy-other.packages`; the ISO builder also reads them when
|
||||
constructing its offline mirror.
|
||||
|
||||
## Explicit resync (`omarchy-reinstall-configs`)
|
||||
|
||||
When an existing user wants to reset to shipped defaults:
|
||||
@@ -344,10 +319,10 @@ return to the packaged default.
|
||||
| --- | --- |
|
||||
| Default file at `~/.config/foo/` | `config/foo/` |
|
||||
| `/etc/` drop-in we own outright | `etc/` |
|
||||
| `/etc/` file owned by an upstream package | `etc/` (see `etc/security/faillock.conf`), then add to `etc-overrides` in `omarchy-settings` PKGBUILD + scriptlet |
|
||||
| Package-owned system file (e.g. systemd user service in `/usr/lib`) | `default/`, then add the `install -Dm644` line in `omarchy-settings` PKGBUILD |
|
||||
| `/etc/` file owned by an upstream package | `default/`, then add to `etc-overrides` in `omarchy-settings` PKGBUILD + scriptlet |
|
||||
| Package-owned system file (e.g. systemd user service/path in `/usr/lib`) | `default/`, document the mapping in `default/package-defaults.tsv`, then add the `install -Dm644` line in `omarchy-settings` PKGBUILD |
|
||||
| Per-user file that's static but lives outside `~/.config` | `default/`, then add `install -Dm644 ... $pkgdir/etc/skel/...` in `omarchy-settings` PKGBUILD |
|
||||
| Runtime tweak that needs `$HOME` or live system state | extend `omarchy-provision-user`, or add a per-user leaf under `install/user/` and wire into `install/user/all.sh` |
|
||||
| Runtime tweak that needs `$HOME` or live system state | extend `omarchy-finalize-user`, or add a per-user leaf under `install/user/` and wire into `install/user/all.sh` |
|
||||
| One-time root-side setup step | `install/config/*.sh` or `install/hardware/*.sh`, wire into `install/config/all.sh` or `install/hardware/all.sh` |
|
||||
| One-time fix for existing installs | `migrations/<unix-timestamp>.sh` |
|
||||
| Package-owned path something else may already write | Prefer a path nothing else writes, such as a vendor drop-in under `/usr/lib`. Otherwise the `--overwrite` entry in `bin/omarchy-update-system-pkgs` has to ship a release before the file |
|
||||
@@ -355,3 +330,9 @@ return to the packaged default.
|
||||
| New stock theme | `themes/<name>/` (+ matching templates under `default/themed/` if they need theme colors) |
|
||||
| User-installed theme | `~/.config/omarchy/themes/<name>/` |
|
||||
| Generated current theme/background state | `~/.local/state/omarchy/current/` |
|
||||
|
||||
## Kitty defaults and user overrides
|
||||
|
||||
Kitty loads `/etc/xdg/kitty/kitty.conf` before `~/.config/kitty/kitty.conf`. The `omarchy-settings` package owns the system file; the user template contains only the active theme include and commented examples for personal overrides. Keeping the theme include in the user file lets users remove it without changing the packaged defaults. Individual inherited keybindings can be unmapped with an empty `map <shortcut>` directive, or all inherited bindings can be cleared with `clear_all_shortcuts yes`.
|
||||
|
||||
The system default uses `allow_remote_control socket-only` so Omarchy can query the active terminal directory over its Unix socket while Kitty rejects remote-control requests arriving through terminal output. Changing this setting requires restarting Kitty. The migration refreshes the exact previous stock config with a backup; customized configs retain their settings and ordering, with only explicit unrestricted `yes`, `y`, or `true` remote-control settings commented out.
|
||||
-167
@@ -1,167 +0,0 @@
|
||||
# The Omarchy menu
|
||||
|
||||
The menu is the `omarchy.menu` plugin of the Quickshell desktop, with its
|
||||
content defined as data in `default/omarchy/omarchy-menu.jsonc` (read at
|
||||
runtime from `$OMARCHY_PATH`) and overlaid by the user's
|
||||
`~/.config/omarchy/extensions/omarchy-menu.jsonc`. The shell parses both files
|
||||
at startup and watches them for changes, so the keybind → IPC → visible path
|
||||
never shells out to parse anything, and edits to either file take effect
|
||||
without restarting the shell. Rendering and behavior live in
|
||||
`shell/plugins/menu/Menu.qml`; the pure logic lives in
|
||||
`shell/plugins/menu/MenuModel.js`, which is plain JavaScript that Node can
|
||||
also load — the shell tests in `test/shell.d/menu-test.sh` and
|
||||
`menu-guards-test.sh` exercise it directly.
|
||||
|
||||
JSONC here means JSON plus comments and trailing commas, stripped by the
|
||||
parser rather than a real JSONC grammar: only whole-line `//` comments are
|
||||
removed, so an inline trailing comment breaks the parse. A file that fails to
|
||||
parse contributes no entries — a broken user extension silently drops every
|
||||
user entry while the shipped menu keeps working.
|
||||
|
||||
## Entry schema
|
||||
|
||||
Entries are object keys. The dotted id is the tree: `trigger.share.file` is a
|
||||
child of `trigger.share`, and an id with no dot sits on the root menu. There
|
||||
is no separate parent field to keep in sync — where an entry appears follows
|
||||
from what it is called (an explicit `parent` is accepted but nothing shipped
|
||||
uses one).
|
||||
|
||||
Kind is inferred rather than declared: an entry with `action` is an action,
|
||||
one with `target` is a link to another submenu, and anything else is a
|
||||
submenu. The fields:
|
||||
|
||||
| Field | Meaning |
|
||||
|---|---|
|
||||
| `icon` | Glyph in the icon column (usually Nerd Font) |
|
||||
| `iconFont` | Font family for the glyph when it differs from the menu font — how the private `omarchy` font's brand glyphs render |
|
||||
| `label` | Visible row title; defaults to the id |
|
||||
| `title` | Header text when the submenu is open; defaults to `label`. Lets a row read "Browser" under Defaults while the open menu says "Default Browser" |
|
||||
| `action` | Shell command to run, detached, when selected |
|
||||
| `target` | Existing submenu id to open; makes the row a link |
|
||||
| `provider` | Runtime row source for this submenu (see Providers) |
|
||||
| `aliases` | Alternate `omarchy menu summon <name>` routes; also searchable |
|
||||
| `description` | Subtitle shown while searching, and extra search text matched by whole word |
|
||||
| `when` / `checked` / `disabled` | Shell conditions (see Guards) |
|
||||
|
||||
Do not add `aliases` to new entries. They are reserved for established
|
||||
alternate names users already type (`power-menu`, `settings`), kept for
|
||||
compatibility — see `AGENTS.md`. Search does not need them: labels, the last
|
||||
id segment, and descriptions are all searchable.
|
||||
|
||||
## Load and merge
|
||||
|
||||
`mergeMenuSources` overlays user entries on the defaults per key: reusing a
|
||||
shipped id replaces only the fields you declare, so an extension can retitle
|
||||
or re-icon a row without re-declaring its action, and an overridden entry
|
||||
keeps its original position in the list. New ids append. A `root` entry is
|
||||
injected if neither file declares one.
|
||||
|
||||
The sample extension at `config/omarchy/extensions/omarchy-menu.jsonc`
|
||||
(refreshed into `~/.config/`) documents the format in its header and ships
|
||||
only comments, so the default state adds nothing.
|
||||
|
||||
## Guards
|
||||
|
||||
`when`, `checked`, and `disabled` are bash conditions. The shell never
|
||||
evaluates them on the open path: all guards in the menu are batched into a
|
||||
single bash process per (re)load and per open, reporting `<id>:<w|c|d>:<0|1>`
|
||||
lines. The menu opens immediately on the previous evaluation's answers, so
|
||||
the batch's runtime is exactly how long a row can contradict the state it
|
||||
describes — which is why the batch works hard to be fast:
|
||||
|
||||
- Package and command presence (`omarchy-pkg-present` and friends) are
|
||||
answered in-process from one `pacman -Q` snapshot instead of a fork per
|
||||
row. The snapshot resolves provides too, so gvim answers for vim.
|
||||
- Commands that several rows read a value from — every Defaults > Browser row
|
||||
compares against `$(omarchy-default-browser)` — run once, with the captured
|
||||
answer substituted into each expression. The reader list is
|
||||
`GUARD_READERS` in `MenuModel.js`; a new `$(omarchy-...)` reader used by
|
||||
more than one row must be added there, and `menu-guards-test.sh` fails the
|
||||
build if it is not.
|
||||
|
||||
The three guards differ in what failure means:
|
||||
|
||||
- `when` hides the row when it fails. A submenu whose visible descendants
|
||||
are all hidden disappears with them (provider-backed submenus stay, since
|
||||
their rows load on demand).
|
||||
- `checked` appends ✓ when it succeeds — the "this is the current choice"
|
||||
marker on defaults, DNS, channel rows.
|
||||
- `disabled` keeps the row listed but dims it, marks it ✓, and makes it
|
||||
unselectable: cursor, pointer, and Enter all step over it, and search omits
|
||||
it. The Install submenus use it so software already on the machine reads as
|
||||
installed rather than vanishing from the list it was installed from — the
|
||||
list stays a catalog of what Omarchy can install. Since a dimmed row means
|
||||
"you already have this", it earns the same ✓ as `checked` does elsewhere.
|
||||
|
||||
Install rows should therefore carry `disabled:` with the presence check, not
|
||||
`when:`; Remove rows are the opposite, hiding via `when:` what is not there
|
||||
to remove. `menu-test.sh` enforces the Install side of this convention.
|
||||
|
||||
## Providers
|
||||
|
||||
A submenu with `provider: "name"` gets its rows at runtime instead of from
|
||||
JSONC. The names are defined by the shell, not the menu file — an extension
|
||||
can point a submenu at an existing provider but cannot declare a new one:
|
||||
|
||||
- `apps` is QML-native: rows come from the shared AppLibrary (desktop
|
||||
entries), carrying image icons, launch feedback, and uninstall support like
|
||||
the launcher. App rows are searchable by their desktop Keywords but never
|
||||
routable, so an installed app cannot capture a menu route (htop ships
|
||||
`Keywords=system;...`, and SUPER+ESCAPE must still open the system menu).
|
||||
- `fonts` and `power-profiles` are bash one-liners in the `providers` map in
|
||||
`Menu.qml`. The contract is one tab-delimited line per row:
|
||||
`label\tvalue\tcurrent`. The row whose value equals `current` gets the ✓
|
||||
icon, and selection runs the spec's `actionFor(value)`. Row ids are
|
||||
`<menuId>.<slugify(value)>`, with a `-` appended on collision so two values
|
||||
that slugify alike cannot silently drop a row.
|
||||
|
||||
A provider marked `volatile` re-runs every time its submenu is entered — a
|
||||
font installed since the shell started shows up without a restart — but not
|
||||
on search keystrokes, which would restart the same enumeration per key.
|
||||
|
||||
`swapProviderRows` in `MenuModel.js` merges the results: rows carry the id of
|
||||
the submenu that produced them, so a provider that runs again drops its
|
||||
previous batch without disturbing static children declared in JSONC. Both it
|
||||
and the app merge return fresh item maps for the caller to assign in one go —
|
||||
writing into a map held by a QML `var` property occasionally loses the write,
|
||||
which used to duplicate launcher rows. Never mutate `root.items` in place.
|
||||
|
||||
Adding a provider means adding an entry to the `providers` map in `Menu.qml`
|
||||
(script, icon, `actionFor`, optionally `volatile`) and pointing a submenu at
|
||||
it with `provider:`.
|
||||
|
||||
## Driving the menu from the CLI
|
||||
|
||||
`bin/omarchy-menu` is a thin wrapper over the standard plugin IPC surface:
|
||||
|
||||
```bash
|
||||
omarchy menu # toggle the root menu
|
||||
omarchy menu toggle system # open at a route, or close if already open
|
||||
omarchy menu summon style.theme # always open (no close-if-visible)
|
||||
omarchy menu close
|
||||
omarchy menu refresh # re-parse the JSONC files
|
||||
omarchy menu ping
|
||||
```
|
||||
|
||||
A route is an item id or a declared alias, case-insensitive, with
|
||||
underscores normalized to dashes. An exact id beats any alias; empty input,
|
||||
`go`, and `menu` mean root; an unknown string falls through as a literal id
|
||||
so a misspelling still attempts to open that id. Summoning a route that
|
||||
resolves to an action — an alias for a leaf, like `screenrecord-stop` — runs
|
||||
the action directly instead of opening an action with no children, and a
|
||||
link is followed to its target. The default Hyprland bindings in
|
||||
`default/hypr/bindings/utilities.lua` all go through this surface
|
||||
(SUPER+SPACE toggles root, SUPER+ESCAPE the system menu, and so on).
|
||||
|
||||
## Select and input modes
|
||||
|
||||
The same plugin doubles as the system's dmenu. `omarchy-menu-select` and
|
||||
`omarchy-menu-input` summon it with a `mode: select` or `mode: input`
|
||||
payload, then block on a tempfile handshake: the shell writes the selection
|
||||
to `selectionFile` and touches `doneFile`, and cancellation (empty
|
||||
selection) exits 1. A select option is `label`, `glyph\tlabel`, or
|
||||
`glyph\tlabel\tsubtext` — the glyph shows but never returns, the subtext
|
||||
renders under the label, filters with it, and comes back as
|
||||
`label\tsubtext` so callers with same-named rows get a stable key. This is
|
||||
how the pickers behind menu actions (`omarchy-menu-plugin`,
|
||||
`omarchy-menu-timezone`, ...) present lists without owning any UI.
|
||||
@@ -1,7 +1,5 @@
|
||||
# Omarchy migrations
|
||||
|
||||
Read this before creating or changing migrations under `migrations/`.
|
||||
|
||||
Omarchy migrations are one-time repair scripts for existing installs. They are
|
||||
used when a package update needs to change state that pacman cannot safely own by
|
||||
itself.
|
||||
@@ -1,199 +0,0 @@
|
||||
# Notifications
|
||||
|
||||
The shell is the notification daemon: `shell/plugins/notifications/Service.qml`
|
||||
hosts a Quickshell `NotificationServer` that claims `org.freedesktop.Notifications`
|
||||
on the session bus. There is no dunst or mako — anything that speaks the
|
||||
freedesktop notification protocol (notify-send, libnotify apps, Chromium web
|
||||
apps) lands in the shell, which renders it as a toast card stacked in the
|
||||
top-right corner. The pure decision logic lives in `NotificationLogic.js`,
|
||||
which is also loadable from Node so `test/shell.d/` can exercise it without
|
||||
a compositor.
|
||||
|
||||
The end-user view (hotkey notices for time, battery, weather) is in
|
||||
`manual/10-notices.md`; this document is the system shape behind it.
|
||||
|
||||
## Toast lifecycle
|
||||
|
||||
A toast lives on screen for at least 5s (low), 8s (normal), or forever
|
||||
(critical), stretched up to 30s if the sender asked for a longer
|
||||
`expire_timeout`. Hovering pauses the countdown, and a content update restarts
|
||||
it — new text deserves a full look. Left-click invokes the default action,
|
||||
right-click or the hover-revealed close button dismisses.
|
||||
|
||||
Every on-screen popup is mirrored to its own file under
|
||||
`~/.local/state/omarchy/notifications/` (one JSON line per file, named
|
||||
`<timestamp>-<id>.json`), so live toasts survive the shell restart that
|
||||
`omarchy-update` performs. When a toast leaves the screen — expiry, dismissal,
|
||||
or click — its file moves into `notifications/history/`, trimmed to the newest
|
||||
ten. That directory *is* the history: `showHistory` replays exactly what has
|
||||
been moved in there. Referenced avatars/images are copied into
|
||||
`notifications/images/`, because senders delete their originals on close.
|
||||
|
||||
`replaces_id` updates never produce a second notification signal: the server
|
||||
writes new content onto the object the service already holds, so the service
|
||||
watches the object's property-change signals and rewrites the row and its file
|
||||
in place, under the popup's original file identity. Restored rows carry ids
|
||||
from a dead server generation (ids restart from 1 each shell process), so
|
||||
they are keyed by timestamp+id and never matched against live objects — a
|
||||
fresh notification reusing an old id must not dismiss or replace them.
|
||||
|
||||
## Silencing
|
||||
|
||||
Do-not-disturb is a single boolean, persisted as the `dnd` key in
|
||||
`~/.local/state/omarchy/notifications.json` and toggled via shell IPC
|
||||
(`omarchy-shell notifications toggleDnd` / `setDnd` / `dndState`).
|
||||
`omarchy-toggle-notification-silencing` wraps the toggle and refreshes the
|
||||
bar's `omarchy.indicators` widget, whose Dnd indicator binds directly to the
|
||||
service's `doNotDisturb` property.
|
||||
|
||||
Two kinds of notification punch through DND, chosen to be intentional and
|
||||
rare:
|
||||
|
||||
- `app_name` = `omarchy-action` — Omarchy's own user-action confirmation
|
||||
toasts ("Theme changed"). The user just did something; their feedback shows.
|
||||
- urgency critical *and* `app_name` = `notify-send` — bare-CLI emergency
|
||||
alerts. Critical alone is not enough, because chat apps abuse it to force
|
||||
visibility; they set `app_name` to their brand, which fails this rule.
|
||||
|
||||
A silenced notification that anyone might look back at is written straight
|
||||
into history — "what did I miss while silenced" is what history is for.
|
||||
Ephemeral ones (the freedesktop `transient` hint, or an `app_name` of
|
||||
`notify-send`/`omarchy-action`) are dropped entirely.
|
||||
|
||||
## The sender contract
|
||||
|
||||
`bin/omarchy-notification-send` is the one way Omarchy code sends
|
||||
notifications — never raw `notify-send`. It calls
|
||||
`org.freedesktop.Notifications.Notify` directly over the session bus (via
|
||||
`busctl --user`), so each value is one typed D-Bus parameter and there is no
|
||||
argv layer that could reinterpret a relayed headline as an option or a hint. Its
|
||||
flags map onto that call:
|
||||
|
||||
| Flag | Becomes | Meaning |
|
||||
|---|---|---|
|
||||
| `-g` / `--glyph` | hint `omarchy-glyph` | Nerd Font glyph for the icon slot when no image icon resolves |
|
||||
| `--exec <program> [args…]` | hint `omarchy-exec-argv` | the click command; consumes the rest of the line, so it comes last. Each word is a discrete argument the shell runs without re-parsing (see below) |
|
||||
| `--image` | hint `image-path` | the standard freedesktop image hint |
|
||||
| `-i` / `--icon` | `app_icon` | themed icon name for the toast |
|
||||
| `--app-name` | `app_name` | defaults to `omarchy-action` |
|
||||
| `-u` / `--urgency` | hint `urgency` (byte) | `low`/`normal`/`critical`; defaults to `low` |
|
||||
| `-t` / `--expire-time` | `expire_timeout` | milliseconds on screen; server default otherwise |
|
||||
|
||||
Unknown flags are a hard error, not a silent pass-through: `--exec` is the only
|
||||
door to a click command, and there is no generic option pass-through to smuggle
|
||||
one through.
|
||||
|
||||
The defaults are the point: an unadorned `omarchy-notification-send "Done"`
|
||||
is a low-urgency user-action toast that pops through DND and is treated as
|
||||
ephemeral noise when silenced.
|
||||
|
||||
The click command is deliberately not a libnotify action. An action keeps the
|
||||
sender blocked waiting for `ActionInvoked`, and dies unanswered whenever the
|
||||
shell restarts underneath it — the installer toasts restart the shell as their
|
||||
first act. Carrying the command as a hint means the shell executes the click
|
||||
itself (detached, so the command outlives the shell process) from the copy it
|
||||
keeps with the popup, which the persistence files preserve: a restored toast
|
||||
clicks through exactly like a live one, and oneshot senders can exit
|
||||
immediately. For third-party clients the click falls back to the libnotify
|
||||
`default` action while the sender is alive, then to focusing the sender's
|
||||
window by class via `omarchy-hyprland-focus-app` — chat apps rarely register
|
||||
an action and just expect click-to-jump.
|
||||
|
||||
### Click commands are argv, never shell strings
|
||||
|
||||
`--exec` consumes the rest of the line as the click command:
|
||||
|
||||
```bash
|
||||
omarchy-notification-send "Download complete" "$title" --exec mpv -- "$file"
|
||||
```
|
||||
|
||||
The caller's shell has already split those words into discrete arguments, and a
|
||||
quoted argument (`"$file"`) stays one argument even with spaces. On the shell
|
||||
side they are run through `Util.execArgv`, which invokes `bash -lc 'exec "$@"'`
|
||||
with the arguments as **positional parameters** — never interpolated into the
|
||||
script text. bash expands `"$@"` without re-tokenizing or re-evaluating it, so a
|
||||
value carrying data an attacker controls — a downloaded video's title, a
|
||||
received filename, a crashed process's name — is only ever a single argument and
|
||||
can never be reparsed as a command. The login shell keeps the PATH and session
|
||||
environment that GUI click targets (the screenshot editor, mpv, xdg-open) expect.
|
||||
|
||||
The critical rule: **the splitting must happen at the call site, not inside the
|
||||
tool.** Passing a single quoted string (`--exec "mpv $title"`) and letting the
|
||||
tool whitespace-split it would hand argument boundaries to whoever controls the
|
||||
string's content — a title with a space could inject an extra option or program.
|
||||
So `--exec` refuses a lone quoted-string argument and points at the unquoted
|
||||
form. There is no "take a command string and sanitize it" path; that is the
|
||||
escaping trap (string-concatenated SQL) the yt-dlp title RCE exploited.
|
||||
|
||||
The shell fails closed on a malformed argv hint (it must be a JSON array of
|
||||
strings whose program is present and not a leading-dash option) rather than
|
||||
running anything it can't validate. A caller can still deliberately name a shell
|
||||
as the program (`--exec sh -c …`), but that runs code because the *developer*
|
||||
wrote it, not because attacker data became a command — a reviewable red flag
|
||||
(greppable as `--exec sh`/`--exec bash`), not an injection. Insulating against a
|
||||
native same-user process is out of scope: it already runs with your privileges
|
||||
and needs no notification to execute code. What is fully closed is untrusted
|
||||
*content* — web notifications can't set the exec hint at all, and any relayed
|
||||
title/filename is confined to inert argument data.
|
||||
|
||||
The sender keeps that last part true structurally rather than leaving it to each
|
||||
caller. Because it calls `Notify` directly, the headline and description are
|
||||
typed string parameters — a relayed value like `--hint=…` or `-rf` is the
|
||||
summary or body, never an option or a hint, and there is no argv/option layer
|
||||
(no `notify-send`) left to reinterpret it. `--exec` is the only thing that can
|
||||
build the `omarchy-exec-argv` hint. (The leading `--` on the `busctl` call is a
|
||||
belt for `busctl`'s own getopt, which would otherwise read a dash-leading value
|
||||
as a `busctl` option; the summary/body themselves are never parsed as options.)
|
||||
|
||||
## Helper commands
|
||||
|
||||
- `omarchy-notification-wait [timeout]` — polls until the shell answers IPC
|
||||
*and* has claimed the bus name. Anything sending near session start or a
|
||||
shell restart uses it, or the toast is sent into the void.
|
||||
- `omarchy-notification-dismiss <summary>` — dismiss by summary substring,
|
||||
used by the first-run toasts once their action has been clicked.
|
||||
- `omarchy-notification-time` / `-battery` — the hotkey notices: one-line
|
||||
low-urgency glyph toasts wrapping `date` and `omarchy-battery-status`.
|
||||
- `omarchy-notification-weather` — despite the name, not a sender: it toggles
|
||||
the `omarchy.weather` shell panel.
|
||||
|
||||
Keybindings live in `default/hypr/bindings/utilities.lua`: `Super+comma`
|
||||
variants map to the IPC methods `dismissOne`, `dismissAll`, `invokeLast`,
|
||||
`showHistory`, and the silencing toggle.
|
||||
|
||||
## How subsystems plug in
|
||||
|
||||
Everything goes through the same sender contract, so the pieces are small:
|
||||
|
||||
- **Low battery** — `omarchy-battery-low` sends a critical toast and runs the
|
||||
`battery-low` hook.
|
||||
- **Crash capture** — `omarchy-crash-watch` follows the systemd-coredump
|
||||
journal stream and announces each crashed program (deduped per minute) as a
|
||||
critical toast whose click runs `omarchy-agent-crash` (via `--exec`, so a
|
||||
hostile process name stays a discrete argument). It waits for the
|
||||
server first: a shell crash takes the notification server down with it, and
|
||||
that crash is the one most worth reporting.
|
||||
- **Pending migrations** — `omarchy-migrate-notify` (from its user service
|
||||
after `graphical-session.target`) waits for the server, then sends a
|
||||
critical toast whose click opens a terminal running `omarchy-migrate`,
|
||||
falling back to printing in the terminal if the hand-off fails.
|
||||
|
||||
## Reminders
|
||||
|
||||
Reminders ride on notifications rather than being their own daemon.
|
||||
`bin/omarchy-reminder <minutes> [message]` creates a transient systemd user
|
||||
timer via `systemd-run --user --collect --on-active=<minutes>m` under the
|
||||
unit name `omarchy-reminder-<minutes>m-<epoch>`; the timer's payload sends the
|
||||
reminder toast, deletes its message file, and refreshes the bar indicator.
|
||||
Custom messages are stashed in `$XDG_RUNTIME_DIR/omarchy-reminders/<unit>.message`
|
||||
since a unit name cannot carry arbitrary text. `--collect` means fired timers
|
||||
leave nothing behind.
|
||||
|
||||
The state therefore lives entirely in systemd: `show` and `clear` enumerate
|
||||
`systemctl --user list-timers "omarchy-reminder-*.timer"` — `show` as a
|
||||
summary toast, `show --json` as the JSON the bar's Reminder indicator polls
|
||||
(refreshed by the same `omarchy-shell -q omarchy.indicators refresh` call the
|
||||
timers and mutations make). `omarchy-reminder -i` summons the
|
||||
`omarchy.reminders` overlay (`shell/plugins/reminders/ReminderFlow.qml`), a
|
||||
two-step minutes/message prompt that shells back out to `omarchy-reminder` to
|
||||
do the setting.
|
||||
+23
-53
@@ -2,12 +2,9 @@
|
||||
|
||||
A single long-running [Quickshell](https://quickshell.org/) instance
|
||||
that hosts the Omarchy desktop. The bar, panels, overlays, menus, and
|
||||
services all run inside as plugins. Hyprland autostart launches the
|
||||
shell via `omarchy-launch-shell`; restart it with `omarchy-restart-shell`.
|
||||
IPC is the canonical way for CLIs to talk to a running shell —
|
||||
`omarchy-shell` forwards a call and fails when the shell is not running
|
||||
(`-q` makes it quiet best-effort; `OMARCHY_SHELL_IPC_TIMEOUT` bounds the
|
||||
wait).
|
||||
services all run inside as plugins. IPC is the canonical way for CLIs
|
||||
to talk to a running shell — `omarchy-shell-ipc` auto-starts it on
|
||||
first call.
|
||||
|
||||
## Plugin manifest
|
||||
|
||||
@@ -37,14 +34,11 @@ wait).
|
||||
|
||||
Only one full bar option is active at a time. The built-in `omarchy.bar` is
|
||||
used when `bar.id` is omitted or when a selected third-party bar cannot load.
|
||||
Panels, overlays, and menus are loaded when summoned. Plugins can set the
|
||||
top-level manifest key `keepLoaded: true` to survive between summons.
|
||||
First-party services are loaded at startup.
|
||||
Panels, overlays, and menus are loaded when summoned. Plugins can set the top-level manifest key `keepLoaded: true` to survive between summons, and to keep a service mounted across plugin hot-reload (so `omarchy.lock` is not destroyed while Hyprland still holds the session lock). First-party services are loaded at startup.
|
||||
|
||||
Entry points are QML `Item`s. Panel, overlay, and menu entry points expose
|
||||
`open(payloadJson)` and `close()` for summon/hide; on load the host injects
|
||||
`omarchyPath`, `shell`, `manifest`, and the registries (`pluginRegistry` /
|
||||
`barWidgetRegistry`) as properties.
|
||||
Entry points are QML `Item`s. Panel, overlay, and menu entry points expose `open(payloadJson)` and `close()` for summon/hide; on load the host injects `omarchyPath`, `shell`, `manifest`, and the registries (`pluginRegistry` / `barWidgetRegistry`) as properties. Built-in plugins receive the trusted host objects. Third-party plugins receive capability-scoped facades instead: ordinary plugins may look up and control only their own service and lifecycle, built-in clones retain narrow source-specific configuration and UI compatibility, menu plugins receive an application-library facade, and plugins can read detached scalar bar state. A full-bar plugin additionally receives detached bar configuration and widget-catalog snapshots, narrow proxies for the non-authentication services used by built-in bar widgets, and lifecycle control over configured non-authentication UI plugins. Authentication capabilities are stamped from trusted first-party manifests, authentication services are kept out of the host's public service map and QML object tree, and third-party registry/configuration snapshots can be changed only locally without mutating host state. The facades are API boundaries, not same-process QML sandboxes: a visual widget shares the host bar's scene and can walk its parent hierarchy to ordinary host objects. Sensitive state must not rely on the facade alone for isolation.
|
||||
|
||||
A third-party replacement bar can render registered widget components, but widgets it hosts receive a service-less entry facade. Allowing the bar to manufacture an own-service facade for an arbitrary widget would also let it retrieve that plugin's live service object. Service-backed third-party widgets therefore retain their full integration only under the trusted built-in bar; a replacement bar may still provide their target-scoped lifecycle and settings operations.
|
||||
|
||||
Full schema: [`shell/services/PluginRegistry.qml`](../shell/services/PluginRegistry.qml).
|
||||
|
||||
@@ -81,28 +75,19 @@ one replaces the active bar, and it is therefore never offered under Disable.
|
||||
Bar widgets may set `barWidget.defaultSection` to `left`, `center`, or `right`;
|
||||
widgets that omit it default to `center`.
|
||||
|
||||
Plugins run as **unsandboxed code** inside `omarchy-shell`. Adding warns you
|
||||
before cloning, plugins land disabled so you can review the code before
|
||||
`omarchy plugin enable`, and updates show a diff before touching anything.
|
||||
Commands confirm in a terminal even when given arguments; without one they
|
||||
refuse rather than guess. Add `--yes` to skip every prompt (the path for
|
||||
scripts and agents).
|
||||
Plugins run as **unsandboxed code** inside `omarchy-shell`. Adding warns you before cloning, plugins land disabled so you can review the code before `omarchy plugin enable`, and updates show a diff before touching anything. Commands confirm in a terminal even when given arguments; without one they refuse rather than guess. Add `--yes` to skip every prompt (the path for scripts and agents). The scoped interfaces remove direct access to authentication services and avoid handing generic cross-plugin service factories to replacement bars, but visual plugins can still traverse ordinary objects in their shared QML scene. Plugin code also has the same user-level file and process access as the shell.
|
||||
|
||||
You can still install by hand: drop a plugin into
|
||||
`~/.config/omarchy/plugins/<id>/`, run `omarchy-shell shell rescanPlugins`, then
|
||||
`omarchy plugin enable <id>`. A bar widget starts in its declared default
|
||||
section; enabling a full bar replaces the one in use. `omarchy bar` drives the
|
||||
bar from the CLI — `use | reset | defaults | position | transparent | put |
|
||||
move | set`, with placement flags such as `--section` and `--index`.
|
||||
section and can be moved with `omarchy bar move`; enabling a full bar
|
||||
replaces the one in use.
|
||||
The lower-level IPC methods remain available through `omarchy-shell shell ...`.
|
||||
|
||||
## IPC
|
||||
|
||||
The shell exposes a `shell` target (the host also registers
|
||||
`image-selector`) plus targets registered by individual plugins, named
|
||||
for the plugin rather than for where it appears: `background`, `osd`,
|
||||
`media`, `notifications`, and per-widget targets such as `omarchy.clock`
|
||||
or `omarchy.power`. There is no `bar` target.
|
||||
The shell exposes a `shell` target plus extra targets registered by
|
||||
individual plugins (`bar`, `image-selector`, …).
|
||||
|
||||
| Method | Effect |
|
||||
|---------------------------------------|---------------------------------|
|
||||
@@ -110,24 +95,17 @@ or `omarchy.power`. There is no `bar` target.
|
||||
| `summon <id> <payloadJson>` | load + open a plugin |
|
||||
| `hide <id>` | close a previously-summoned |
|
||||
| `toggle <id> <payloadJson>` | summon if closed, hide if open |
|
||||
| `togglePanelAt <section> <index>` | toggle the panel at a bar position |
|
||||
| `call <id> <method> <arg>` | call an already-loaded plugin |
|
||||
| `rescanPlugins` | re-walk plugin dirs and hot-reload plugin code |
|
||||
| `reloadConfig` | reload shell.json |
|
||||
| `applyTheme <colorsB64> <shellB64>` | push theme colors + shell.toml |
|
||||
| `toggleBarTransparency` | flip the bar background between solid and transparent |
|
||||
| `setPluginEnabled <id> <"true"\|…>` | flip enabled bit (`ok` / `unknown`) |
|
||||
| `enablePlugin <id> <placementJson>` | enable and place in one mutation |
|
||||
| `putBarWidget <id> <placementJson>` | place a widget only where absent (`omarchy bar put`) |
|
||||
| `moveBarWidget <id> <placementJson>` | move a configured widget |
|
||||
| `setBarWidget <id> <key> <valueJson> <selectorJson>` | set an inline widget option |
|
||||
| `listPlugins` | JSON of every discovered plugin |
|
||||
| `listShellConfig` | effective shell.json as JSON |
|
||||
| `debugBarGeometry` | bar geometry dump for debugging |
|
||||
|
||||
`setPluginEnabled` takes a string; only literal `"true"` enables. Methods
|
||||
answer on stdout with exit 0 — `ok` on success, `unknown` or an error
|
||||
string on a miss.
|
||||
`setPluginEnabled` takes a string; only literal `"true"` enables.
|
||||
|
||||
## shell.json
|
||||
|
||||
@@ -142,7 +120,8 @@ string on a miss.
|
||||
"id": "omarchy.bar",
|
||||
"position": "top",
|
||||
"transparent": false,
|
||||
"centerAnchor": "omarchy.clock",
|
||||
"centerAnchor": "calendar",
|
||||
"fontFamily": "JetBrainsMono Nerd Font",
|
||||
"layout": {
|
||||
"left": [ { "id": "omarchy.menu" } ],
|
||||
"center": [ { "id": "omarchy.clock", "format": "HH:mm" } ],
|
||||
@@ -165,9 +144,10 @@ Rules:
|
||||
3. Settings are inline on the entry. No `config:` sub-object, no
|
||||
merge layers.
|
||||
4. Built-in bar widget ids are namespaced (`omarchy.clock`, `omarchy.audio`, …).
|
||||
The migration rewrites older ids such as `Clock` and `AudioPanel` forward.
|
||||
5. Third-party enabled ⇔ present; for full bar options that means `bar.id`.
|
||||
First-party non-bar plugins are enabled unless listed in `disabledPlugins[]`.
|
||||
6. `barWidget.allowMultiple: true` in the manifest permits multiple instances.
|
||||
6. `allowMultiple: true` in the manifest permits multiple instances.
|
||||
7. `idle.screensaver` and `idle.lock` are seconds since user idle began.
|
||||
8. `version: 1` is required.
|
||||
|
||||
@@ -175,12 +155,6 @@ Rules:
|
||||
user `shell.json` exists, defaults are used verbatim. Once the user
|
||||
customizes, `shell.json` is canonical — there is no deep-merge.
|
||||
|
||||
`shell.json` is shell configuration; theme tokens live in `shell.toml`
|
||||
(next section). Both are current — they answer different questions. A
|
||||
machine-level `~/.config/omarchy/shell.toml` is watched live by the
|
||||
shell and its keys win over the active theme's `shell.toml`, so
|
||||
overrides like `omarchy display text size` survive theme switches.
|
||||
|
||||
## Theme tokens
|
||||
|
||||
See [`theming.md`](theming.md) for the full theme/template workflow,
|
||||
@@ -189,23 +163,20 @@ including generated `*.tpl` files, gradient helpers, and shell border syntax.
|
||||
Themes ship colors in `themes/<name>/colors.toml` and surface roles +
|
||||
sizing in `themes/<name>/shell.toml`. Defaults are generated from
|
||||
`default/themed/shell.toml.tpl`; a theme may also drop a hand-written
|
||||
`shell.toml` next to its `colors.toml` to replace the generated file,
|
||||
or override a single section with `shell.<section>.toml`, merged in by
|
||||
`omarchy-theme-set-templates` (see [`theming.md`](theming.md)).
|
||||
`shell.toml` next to its `colors.toml` to replace the generated file.
|
||||
|
||||
`colors.toml` uses `foreground` and `background` for the foundational
|
||||
text/background palette, exposed to QML as `Color.foreground` and
|
||||
`Color.background`.
|
||||
|
||||
The shell exposes these tokens to QML via three singletons in
|
||||
The shell exposes these tokens to QML via two singletons in
|
||||
`qs.Commons`:
|
||||
|
||||
- `Color` — palette (`foreground`, `background`, `accent`, `urgent`)
|
||||
and per-surface roles (`Color.bar.*`, `Color.popups.*`,
|
||||
`Color.tooltip.*`, `Color.notifications.*`, `Color.menu.*`,
|
||||
`Color.polkit.*`, `Color.lock.*`, `Color.imagePicker.*`). Clipboard
|
||||
and emojis share `Color.menu.*`; the `[launcher]` section is consumed
|
||||
by the launcher outside shell QML.
|
||||
`Color.launcher.*`, `Color.imagePicker.*`, `Color.polkit.*`,
|
||||
`Color.lock.*`). Clipboard and emojis share `Color.menu.*`.
|
||||
- `Style` — structural tokens (`cornerRadius`), shared interactive
|
||||
state tokens/helpers, spacing (`Style.spacing.*` / `Style.space(px)`),
|
||||
the type scale (`Style.font.*`), and bar dimensions
|
||||
@@ -398,6 +369,5 @@ Then `~/.config/omarchy/bar/modules/gpu.qml` (or set `source` to point
|
||||
elsewhere). The module is an `Item` and receives `bar`, `moduleName`,
|
||||
`settings` properties. `bar` exposes `foreground` / `background` /
|
||||
`urgent` / `fontFamily` / `position` / `vertical` / `barSize`, plus
|
||||
`run(cmd)`, `showTooltip(t, s)` / `hideTooltip(t)`,
|
||||
`requestPopout(o)` / `releasePopout(o)`. To shell-quote arguments for
|
||||
`run`, use `Util.shellQuote(v)` from `qs.Commons`.
|
||||
`run(cmd)`, `shellQuote(v)`, `showTooltip(t, s)` / `hideTooltip(t)`,
|
||||
`requestPopout(o)` / `releasePopout(o)`.
|
||||
-138
@@ -1,138 +0,0 @@
|
||||
# Testing
|
||||
|
||||
How the non-graphical test suites are organized: what each runner owns, the
|
||||
protocol test files speak, and the conventions that keep them runnable on any
|
||||
machine — including headless CI sandboxes with no compositor. The graphical
|
||||
acceptance suite is a separate thing that drives a live session in a disposable
|
||||
VM; see [`agents/skills/acceptance-tests.md`](../agents/skills/acceptance-tests.md).
|
||||
|
||||
## Suite map
|
||||
|
||||
`./test/all` runs both suites below and keeps going when one fails, so a single
|
||||
failure cannot hide the other suite behind it. It reports the failed suites at
|
||||
the end and exits non-zero.
|
||||
|
||||
- **`./test/cli`** — one big script, one suite. It owns the CLI router: help
|
||||
and group rendering, route resolution, aliases, hidden commands, and the
|
||||
guarantee that a trailing `--help` never executes the target. It also owns
|
||||
the metadata lint — every `omarchy-*` executable under `bin/` is checked for a
|
||||
`# omarchy:summary=` header and against removed or redundant fields — plus
|
||||
the theme pipeline: template rendering (`omarchy-theme-set-templates`,
|
||||
`omarchy-theme-color`, `omarchy-theme-osc`), the theme sync commands
|
||||
(tmux, GNOME, VS Code, Pi, Claude) run against stub binaries and a fake
|
||||
`$HOME`, and the theme-state migrations.
|
||||
- **`./test/shell`** — runs every `test/shell.d/*-test.sh` (except
|
||||
`base-test.sh` itself). Each file is an independent suite covering one area:
|
||||
a shell plugin, a `bin/` command, a config invariant, a migration. This is
|
||||
where new tests go.
|
||||
- **Acceptance** — everything that needs a real desktop doing real things.
|
||||
Deliberately excluded from `./test/all`; it runs in a VM, not the
|
||||
development session.
|
||||
|
||||
A new shell test only needs the right name: drop `<area>-test.sh` into
|
||||
`test/shell.d/` and `./test/shell` picks it up automatically. Shared fixtures
|
||||
live under `test/shell.d/fixtures/`.
|
||||
|
||||
## The base-test.sh contract
|
||||
|
||||
Every shell test starts the same way:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
```
|
||||
|
||||
`base-test.sh` refuses to be executed directly — it is a library. It discovers
|
||||
the repo root from its own location and exports it as `ROOT`, so tests
|
||||
reference files as `$ROOT/bin/...` and never depend on the caller's working
|
||||
directory or an installed Omarchy.
|
||||
|
||||
Assertions are TAP-flavored and blunt:
|
||||
|
||||
- `pass "description"` prints `ok - description`.
|
||||
- `fail "description" [detail]` prints the optional detail and
|
||||
`not ok - description` to stderr, then **exits the file**. There is no
|
||||
counting or continuing within a file: the first failed assertion ends it,
|
||||
which keeps later assertions from reporting against state the failure
|
||||
already invalidated.
|
||||
- `require_command <cmd>` fails the file when a needed tool is absent.
|
||||
|
||||
The runner compensates for that early exit: `./test/shell` continues past a
|
||||
failing file and summarizes the failures at the end. Aborting the whole run at
|
||||
the first bad file once let a single packaging failure mask 114 of 134 files.
|
||||
Failure granularity is therefore per file inside a run, per assertion inside a
|
||||
file.
|
||||
|
||||
## Compositor-dependent tests
|
||||
|
||||
Some tests launch Quickshell or query Hyprland, but the suite must stay green
|
||||
on headless machines. `require_compositor "description"` handles this: when no
|
||||
compositor answers it prints `ok - no Wayland compositor; skipping ...` and
|
||||
exits 0 — a skip is a passing test — and otherwise returns so the file
|
||||
proceeds.
|
||||
|
||||
The probe is more than an environment check, because `WAYLAND_DISPLAY` only
|
||||
proves the variable was inherited. Sandboxes pass the environment through
|
||||
while blocking `$XDG_RUNTIME_DIR`, so Quickshell clears a bare variable check
|
||||
and then aborts inside QGuiApplication — a core dump per launch where a skip
|
||||
belonged. So `compositor_reachable` checks the socket actually exists, then
|
||||
asks Hyprland itself (`hyprctl -j monitors`, retried, and only when
|
||||
`HYPRLAND_INSTANCE_SIGNATURE` makes it askable), since a compositor that died
|
||||
mid-session leaves its socket behind. When the compositor is reachable,
|
||||
`require_compositor` also sets `ulimit -c 0`: Quickshell leaves through
|
||||
`qFatal()` if its connection drops mid-run, and the test should fail without
|
||||
writing a core dump as debris.
|
||||
|
||||
Gate only what needs gating — put `require_compositor` in files whose runtime
|
||||
half needs a live session, and keep static analysis of the same area in code
|
||||
that runs unconditionally before or beside it.
|
||||
|
||||
## Unit-testing shell JavaScript from bash
|
||||
|
||||
The Quickshell plugins keep their logic in plain `.js` modules
|
||||
(`shell/plugins/menu/MenuModel.js`, `bar/BarModel.js`, ...) that end in a
|
||||
guarded `if (typeof module !== "undefined") module.exports = {...}` block. QML
|
||||
imports them directly and ignores the guard; Node loads them as CommonJS. That
|
||||
dual citizenship is what makes the shell's model logic unit-testable without a
|
||||
compositor.
|
||||
|
||||
`run_node_test` is the bridge: it prepends a JS prelude to a heredoc and pipes
|
||||
the result into `node`. The prelude mirrors the bash assertion protocol
|
||||
(`pass`, `fail`, `assert`, `assertEqual`, `assertDeepEqual` — same
|
||||
`ok`/`not ok` lines, same exit-on-first-failure) and provides `root` (from the
|
||||
exported `ROOT`), `path`, and `requireFromRoot(relativePath)`:
|
||||
|
||||
```bash
|
||||
run_node_test <<'JS'
|
||||
const menu = requireFromRoot('shell/plugins/menu/MenuModel.js')
|
||||
|
||||
const parsed = menu.parseMenuJsonc('{ "items": { "root": { "label": "Go" }, }, }')
|
||||
assertEqual(parsed.length, 1, 'menu parses JSONC with trailing commas')
|
||||
JS
|
||||
```
|
||||
|
||||
Roughly a quarter of the shell test files use this to test parsing, merging,
|
||||
and layout logic as pure functions, reserving compositor-gated tests for what
|
||||
only a live session can prove.
|
||||
|
||||
## Conventions worth copying
|
||||
|
||||
- **Stub the world, run the real code.** Tests build a scratch `bin/` of stub
|
||||
executables (`sudo`, `tmux`, `gsettings`, helper commands) that log their
|
||||
arguments to a file, prepend it to `PATH`, and then run the real script
|
||||
under test. Assertions grep the call log and the files the script wrote.
|
||||
- **Fake `$HOME`, real `$OMARCHY_PATH`.** Anything touching user state runs
|
||||
with `HOME` pointed at a `mktemp -d` directory (cleaned up via
|
||||
`trap ... EXIT`) and `OMARCHY_PATH="$ROOT"`, so tests exercise the checkout
|
||||
without touching the developer's machine.
|
||||
- **Migrations run directly.** A migration test builds the legacy state in a
|
||||
fake `$HOME`, runs `bash -euo pipefail "$ROOT/migrations/<ts>.sh"`, and
|
||||
asserts the resulting state — including running it twice to prove
|
||||
idempotence, and once against non-legacy state to prove it leaves user
|
||||
customization alone.
|
||||
- **Assert the invariant, not the snapshot.** Config tests pin the property a
|
||||
test is named for (this widget stays adjacent to that one) rather than whole
|
||||
structures, so unrelated churn does not fail them.
|
||||
+5
-7
@@ -101,8 +101,7 @@ shell palette is loaded from:
|
||||
`color4`
|
||||
- `muted` — de-emphasized elements (comments, placeholders, dividers); also
|
||||
serves as ANSI `color8`
|
||||
- `red` / `color1` — populate the shell's urgent role; there is no `urgent`
|
||||
palette key (one defined in `colors.toml` is ignored)
|
||||
- `urgent` / `red` / `color1`
|
||||
|
||||
Themes and user templates using the legacy short names remain supported.
|
||||
Canonical names take precedence when both forms are defined, and resolved
|
||||
@@ -342,11 +341,10 @@ BorderSurface {
|
||||
}
|
||||
```
|
||||
|
||||
Use `Border.surfaceSpec(section, token, fallbackColor, fallbackWidth, alphaKey)`
|
||||
for shell theme tokens (the optional `alphaKey` names the alpha token, e.g.
|
||||
`"border-alpha"`), `Border.controlSpec(state, foreground, accent, urgent)` for
|
||||
shared controls, and `Border.flat(color, width)` for a deliberate local border
|
||||
that should not be overridden by the active theme. `Color.<section>.border` is the
|
||||
Use `Border.surfaceSpec(section, token, fallbackColor, fallbackWidth)` for
|
||||
shell theme tokens, `Border.controlSpec(state, foreground, accent)` for shared
|
||||
controls, and `Border.flat(color, width)` for a deliberate local border that
|
||||
should not be overridden by the active theme. `Color.<section>.border` is the
|
||||
flat first-stop color for consumers that cannot render full border specs.
|
||||
|
||||
## Hyprland templates
|
||||
|
||||
+6
-31
@@ -30,7 +30,7 @@ The design goal is:
|
||||
|
||||
## Migration layout
|
||||
|
||||
See [`migrations.md`](../agents/skills/migrations.md) for the full migration model, authoring
|
||||
See [`migrations.md`](migrations.md) for the full migration model, authoring
|
||||
guidelines, and troubleshooting notes.
|
||||
|
||||
Migrations live in:
|
||||
@@ -82,7 +82,7 @@ Omarchy update command, the hook exits non-zero with `AbortOnFail`, which stops
|
||||
the transaction before packages are changed.
|
||||
|
||||
`omarchy-update-system-pkgs`, `omarchy-refresh-pacman`, `omarchy-reinstall-pkgs`,
|
||||
`omarchy-channel-set`, and the v4 upgrader run pacman through:
|
||||
and the v4 upgrader run pacman through:
|
||||
|
||||
```bash
|
||||
env OMARCHY_UPDATE_PACMAN=1 pacman ...
|
||||
@@ -116,14 +116,9 @@ omarchy-update
|
||||
├─ omarchy-update-lock
|
||||
│ └─ acquire the update lock and run omarchy-update inside it
|
||||
├─ omarchy-update-requires-free-space
|
||||
│ └─ abort below the configured free-space threshold on /
|
||||
│ └─ check free space on / and warn below the configured threshold
|
||||
├─ confirm unless -y
|
||||
├─ omarchy-update-pkg-prune
|
||||
│ └─ trim the pacman cache to two versions per package, deliberately
|
||||
│ before the snapshot since the cache lives on the snapshotted subvolume
|
||||
├─ create snapper snapshot (skipped silently without snapper; snapper
|
||||
│ installed but unconfigured fails the snapshot loudly, pointing at
|
||||
│ install/config/snapper.sh, and the update continues without one)
|
||||
├─ create snapper snapshot, if snapper is installed
|
||||
├─ omarchy-update-stay-awake start
|
||||
├─ run package updates, migrations, hooks, and log analysis
|
||||
├─ omarchy-update-status
|
||||
@@ -137,9 +132,6 @@ Important behavior:
|
||||
|
||||
- In dev-link mode, `omarchy update` fast-forwards the active checkout from its
|
||||
configured upstream before changing system packages or running migrations.
|
||||
- `-y` exports `OMARCHY_UPDATE_UNATTENDED=1` — a promise not to ask anything.
|
||||
Steps that would prompt (orphan removal, conflict handoff) report and skip
|
||||
instead of blocking.
|
||||
- The free-space requirement uses a 10 GiB threshold and stops the update before
|
||||
confirmation when it is not met. If free space cannot be determined, the
|
||||
check is silently skipped. Set `OMARCHY_UPDATE_FORCE=1` to bypass the check.
|
||||
@@ -243,20 +235,6 @@ Exit codes:
|
||||
The widget runs this check on shell startup and every six hours. Clicking the
|
||||
update icon launches `omarchy-update` in a floating terminal.
|
||||
|
||||
## Channels and versions
|
||||
|
||||
Updates install whatever the active channel points at. `omarchy-channel-set
|
||||
<stable|rc|edge|dev>` switches channels: the three package channels select
|
||||
which pacman repo the mirrorlist points at (and swap between the `omarchy` and
|
||||
`omarchy-dev` packages through a guard-allowed pacman run), while `dev` links
|
||||
the runtime to a git checkout via the dev-link mechanism, after which
|
||||
`omarchy update` fast-forwards that checkout instead of upgrading a package.
|
||||
|
||||
There is no version file at runtime. `omarchy-version` derives the version from
|
||||
`pacman -Q` on whichever package is installed, or reports `dev (<hash>)` for a
|
||||
linked checkout, and `omarchy-version-channel` sniffs the mirrorlist and
|
||||
pacman.conf to answer which channel is active.
|
||||
|
||||
## Update-related binaries
|
||||
|
||||
This inventory is intentionally opinionated. Some commands are useful as stable
|
||||
@@ -272,17 +250,14 @@ scripts.
|
||||
| `omarchy-update-confirm` | Gum confirmation copy for `omarchy update`. | **Question.** Could be inlined into `omarchy-update`; separate file only helps keep copy isolated. |
|
||||
| `omarchy-update-dev` | Fast-forwards the active dev-linked checkout from its configured upstream; no-ops for package-backed installs. | **Keep.** Runs before package updates so a checkout conflict stops the update before system mutation. |
|
||||
| `omarchy-update-keyring` | Ensures Omarchy keyring and Arch keyring are current before the main transaction. | **Keep, but review.** It uses targeted `pacman -Sy` for keyring bootstrapping; acceptable for this special case but should remain tightly scoped. |
|
||||
| `omarchy-update-system-pkgs` | Runs `sudo env OMARCHY_UPDATE_PACMAN=1 pacman -Syu --noconfirm` with `--overwrite '/usr/share/omarchy/*'`, capturing stderr to a report file; on failure it execs `omarchy-update-system-pkgs-when-conflicted`. | **Keep for now.** Small leaf command, clear/testable. |
|
||||
| `omarchy-update-system-pkgs-when-conflicted` | Hidden conflict handler: quarantines unowned conflicting files under `/var/lib/omarchy/replaced`, retries the upgrade once, restores files the upgrade didn't claim, and hands package-vs-package conflicts to an interactive pacman run (never under `-y`). | **Keep internal/hidden.** Keeps conflict recovery out of the happy path. |
|
||||
| `omarchy-update-pkg-prune` | Trims the pacman cache to two versions per package (`paccache -rk2`) before the snapshot, keeping the offline downgrade path while capping snapshot growth. | **Keep internal/hidden.** |
|
||||
| `omarchy-update-requires-free-space` | Aborts the update below a 10 GiB free-space threshold on `/`; silently skipped when free space cannot be determined; `OMARCHY_UPDATE_FORCE=1` bypasses. | **Keep internal/hidden.** |
|
||||
| `omarchy-update-system-pkgs` | Runs `sudo env OMARCHY_UPDATE_PACMAN=1 pacman -Syu --noconfirm` with targeted transition `--overwrite` entries so the ALPM guard allows the transaction and early package-layout conflicts are handled. | **Keep for now.** Small leaf command, clear/testable. |
|
||||
| `omarchy-migrate` | Public migration command. Waits for pacman, then runs all pending migrations for the current user. Supports `--pending`. | **Keep.** This replaces the discarded `omarchy-update-user-finalize` name and no longer needs `--force`. |
|
||||
| `omarchy-update-pacman-guard` | ALPM pre-transaction guard that aborts direct `pacman -Syu` style upgrades unless Omarchy set `OMARCHY_UPDATE_PACMAN=1` or the user explicitly set `OMARCHY_ALLOW_DIRECT_PACMAN=1`. | **Keep internal/hidden.** This is what nudges users back to `omarchy update`. |
|
||||
| `omarchy-migrate-notify` | Internal login-time notification helper. Uses `omarchy-migrate --pending` and shows a notification only when this user has pending migrations. | **Keep internal/hidden.** Clear name now that the public command is `omarchy-migrate`. |
|
||||
| `omarchy-update-user-notify` | Hidden compatibility wrapper for `omarchy-migrate-notify`. | **Temporary.** Keep only for old callers. |
|
||||
| `omarchy-update-available` | Update checker for shell widget and post-update refresh. | **Keep.** Could eventually be renamed `omarchy-update-check`, but current name matches widget semantics. |
|
||||
| `omarchy-update-aur-pkgs` | Updates AUR packages with `yay -Sua` if foreign packages exist and AUR is reachable. | **Question.** Omarchy is package-backed now, but users may still install AUR packages. Keep for now. |
|
||||
| `omarchy-update-mise` | Runs `MISE_MINIMUM_RELEASE_AGE=0 mise up` for mise-managed tools — the override of mise's release-age cooldown is the point. | **Keep.** Mise-managed tools are intentionally part of the blessed update path. |
|
||||
| `omarchy-update-mise` | Runs `mise up` for mise-managed tools. | **Keep.** Mise-managed tools are intentionally part of the blessed update path. |
|
||||
| `omarchy-update-orphan-pkgs` | Lists orphans and prompts before removal; noninteractive mode never removes. | **Keep for now.** Safe because it is prompt-only. |
|
||||
| `omarchy-update-analyze-logs` | Scans `/tmp/omarchy-update.log` for known failure patterns, currently initramfs generation. | **Keep/expand.** Useful safety net; should grow only for high-signal checks. |
|
||||
| `omarchy-update-restart` | Prompts for reboot after kernel/Hyprland updates, restarts components with `restart-*-required` markers, and always restarts the shell. | **Keep.** Important final step; may eventually include service-restart checks. |
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# Keep Cursor optional while applying these options whenever mise installs it.
|
||||
# Expose only its launcher so the bundled Node cannot shadow the user's Node.
|
||||
[tool_alias]
|
||||
cursor-agent = 'http:cursor-agent[bin_path=bin,postinstall=mkdir -p "$MISE_TOOL_INSTALL_PATH/bin" && ln -sfn ../dist-package/cursor-agent "$MISE_TOOL_INSTALL_PATH/bin/cursor-agent"]'
|
||||
@@ -1 +0,0 @@
|
||||
%wheel ALL=(ALL) NOPASSWD: /usr/bin/asdcontrol
|
||||
@@ -0,0 +1,5 @@
|
||||
# omarchy-sudo-passwordless writes /etc/sudoers.d/99-omarchy-nopasswd-<user> and
|
||||
# arms a transient systemd-run timer to remove it again. Transient units do not
|
||||
# survive a reboot, so remove any remaining grant during early boot. Boot-only
|
||||
# (r!) ensures a later systemd-tmpfiles --remove cannot cut a live grant short.
|
||||
r! /etc/sudoers.d/99-omarchy-nopasswd-*
|
||||
@@ -0,0 +1,35 @@
|
||||
# Omarchy defaults. Put personal overrides in ~/.config/kitty/kitty.conf.
|
||||
|
||||
# Font
|
||||
font_family JetBrainsMono Nerd Font
|
||||
bold_italic_font auto
|
||||
font_size 9.0
|
||||
|
||||
# Window
|
||||
window_padding_width 14
|
||||
hide_window_decorations yes
|
||||
confirm_os_window_close 0
|
||||
|
||||
# Keybindings
|
||||
map ctrl+insert copy_to_clipboard
|
||||
map shift+insert paste_from_clipboard
|
||||
# Send Shift+Enter as CSI-u so TUIs can distinguish it from Enter.
|
||||
map shift+enter send_text all \e[13;2u
|
||||
# Kitty legacy encoding sends Alt+Shift+Enter the same as Alt+Enter; send CSI-u so tmux can match M-S-Enter.
|
||||
map alt+shift+enter send_text all \e[13;4u
|
||||
|
||||
# Allow local cwd lookup, but reject remote control through terminal output.
|
||||
allow_remote_control socket-only
|
||||
listen_on unix:${XDG_RUNTIME_DIR}/omarchy-kitty-{kitty_pid}
|
||||
|
||||
# Aesthetics
|
||||
cursor_shape block
|
||||
cursor_blink_interval 0
|
||||
shell_integration no-cursor
|
||||
enable_audio_bell no
|
||||
|
||||
# Minimal Tab bar styling
|
||||
tab_bar_edge bottom
|
||||
tab_bar_style powerline
|
||||
tab_powerline_style slanted
|
||||
tab_title_template {title}{' :{}:'.format(num_windows) if num_windows > 1 else ''}
|
||||
@@ -7,6 +7,5 @@ run_logged "$OMARCHY_INSTALL/config/ssh-command-path.sh"
|
||||
run_logged "$OMARCHY_INSTALL/config/ssh-keepalive.sh"
|
||||
run_logged "$OMARCHY_INSTALL/config/docker.sh"
|
||||
run_logged "$OMARCHY_INSTALL/config/snapper.sh"
|
||||
run_logged "$OMARCHY_INSTALL/config/locate.sh"
|
||||
run_logged "$OMARCHY_INSTALL/config/enable-services.sh"
|
||||
run_logged "$OMARCHY_INSTALL/config/firewall.sh"
|
||||
@@ -1,32 +0,0 @@
|
||||
UPDATEDB_CONF_PATH="${OMARCHY_UPDATEDB_CONF_PATH:-/etc/updatedb.conf}"
|
||||
|
||||
echo "Configuring locate to skip Btrfs snapshots and index Btrfs subvolumes"
|
||||
|
||||
[[ -f $UPDATEDB_CONF_PATH ]] || exit 0
|
||||
|
||||
# updatedb refuses to run at all on a config that defines a variable twice, so
|
||||
# every setting here is rewritten where it already stands and only appended
|
||||
# when the file has no line for it.
|
||||
|
||||
# Btrfs subvolume mounts (like /home) look like bind mounts, so pruning
|
||||
# bind mounts leaves them out of the index entirely.
|
||||
if grep -qE '^[[:space:]]*PRUNE_BIND_MOUNTS[[:space:]]*=' "$UPDATEDB_CONF_PATH"; then
|
||||
sed -i -E 's|^[[:space:]]*PRUNE_BIND_MOUNTS[[:space:]]*=.*|PRUNE_BIND_MOUNTS = "no"|' "$UPDATEDB_CONF_PATH"
|
||||
else
|
||||
printf '%s\n' 'PRUNE_BIND_MOUNTS = "no"' >>"$UPDATEDB_CONF_PATH"
|
||||
fi
|
||||
|
||||
# Snapper snapshots are nested subvolumes reached by plain directory
|
||||
# traversal, so without this updatedb indexes the system once per snapshot.
|
||||
if grep -qE '^[[:space:]]*PRUNEPATHS[[:space:]]*=' "$UPDATEDB_CONF_PATH"; then
|
||||
# updatedb only accepts quoted values and allows a comment after them. Read
|
||||
# back what the machine already prunes and write the whole setting out again
|
||||
# rather than splicing into a line of unknown shape.
|
||||
pruned=$(sed -nE 's|^[[:space:]]*PRUNEPATHS[[:space:]]*=[[:space:]]*"([^"]*)".*|\1|p' "$UPDATEDB_CONF_PATH" | tail -n 1)
|
||||
|
||||
if [[ " $pruned " != *" /.snapshots "* ]]; then
|
||||
sed -i -E "s|^[[:space:]]*PRUNEPATHS[[:space:]]*=.*|PRUNEPATHS = \"/.snapshots${pruned:+ $pruned}\"|" "$UPDATEDB_CONF_PATH"
|
||||
fi
|
||||
else
|
||||
printf '%s\n' 'PRUNEPATHS = "/.snapshots"' >>"$UPDATEDB_CONF_PATH"
|
||||
fi
|
||||
@@ -4,7 +4,6 @@ run_logged "$OMARCHY_INSTALL/hardware/dell-xps-touchpad-haptics.sh"
|
||||
run_logged "$OMARCHY_INSTALL/hardware/surface.sh"
|
||||
|
||||
run_logged "$OMARCHY_INSTALL/hardware/network.sh"
|
||||
run_logged "$OMARCHY_INSTALL/hardware/input-group.sh"
|
||||
run_logged "$OMARCHY_INSTALL/hardware/set-wireless-regdom.sh"
|
||||
run_logged "$OMARCHY_INSTALL/hardware/fix-fkeys.sh"
|
||||
run_logged "$OMARCHY_INSTALL/hardware/fix-synaptic-touchpad.sh"
|
||||
@@ -25,10 +24,6 @@ run_logged "$OMARCHY_INSTALL/hardware/intel/fred.sh"
|
||||
run_logged "$OMARCHY_INSTALL/hardware/intel/fix-wifi7-eht.sh"
|
||||
run_logged "$OMARCHY_INSTALL/hardware/intel/sof-firmware.sh"
|
||||
|
||||
# Rebuilds the boot image, so it has to follow the Panther Lake kernel swap
|
||||
# above rather than sit with the other Dell leaf at the top of this file.
|
||||
run_logged "$OMARCHY_INSTALL/hardware/dell-xps13-sidecar-amps.sh"
|
||||
|
||||
run_logged "$OMARCHY_INSTALL/hardware/asus/fix-asus-ptl-display-backlight.sh"
|
||||
run_logged "$OMARCHY_INSTALL/hardware/asus/fix-asus-ptl-b9406-display.sh"
|
||||
run_logged "$OMARCHY_INSTALL/hardware/asus/fix-asus-ptl-b9406-touchpad.sh"
|
||||
|
||||
Loaded 100 of 402 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user