Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions bin/omarchy-agent-skill-link
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/bin/bash

# omarchy:summary=Link a skill directory into every agent's skills directory, or take those links back
# omarchy:args=<skill-dir> [--remove]
# omarchy:examples=omarchy agent skill-link $OMARCHY_PATH/default/agents/optional-skills/cua-omarchy | omarchy agent skill-link $OMARCHY_PATH/default/agents/optional-skills/cua-omarchy --remove

# omarchy-provision-user links every skill under default/agents/skills for
# everyone. A skill that only makes sense once the tool it describes is on the
# machine lives under default/agents/optional-skills instead, and the tool's
# install flow links it here into the same agent directories; the matching
# remove flow takes the links back with --remove.

set -euo pipefail

usage() {
echo "Usage: omarchy-agent-skill-link <skill-dir> [--remove]" >&2
exit 1
}

skill_dir=""
remove=false
for arg in "$@"; do
case $arg in
--remove) remove=true ;;
-*) usage ;;
*) skill_dir=$arg ;;
esac
done

[[ -n $skill_dir ]] || usage

if [[ $remove == false && ! -f $skill_dir/SKILL.md ]]; then
echo "No skill at $skill_dir: expected a directory holding a SKILL.md." >&2
exit 1
fi

skill_dir=$(realpath -m -- "$skill_dir")
name=${skill_dir##*/}

# Hermes profiles are only linked when they already exist; nothing here
# creates one.
agent_dirs=(
"$HOME/.agents/skills"
"$HOME/.claude/skills"
"$HOME/.codex/skills"
"$HOME/.pi/agent/skills"
"$HOME/.gemini/config/skills"
"$HOME/.hermes/skills"
)
for profile in "$HOME"/.hermes/profiles/*/; do
[[ -d $profile ]] && agent_dirs+=("${profile}skills")
done

for dir in "${agent_dirs[@]}"; do
link="$dir/$name"

if [[ $remove == true ]]; then
# Only a link to this skill: a skill of the same name the user keeps
# there themselves is not ours to remove.
if [[ -L $link && $(realpath -m -- "$link") == "$skill_dir" ]]; then
rm -- "$link"
fi
elif [[ -e $link && ! -L $link ]]; then
# ln would otherwise drop the link inside the directory that is there.
echo "Leaving $link alone: it is not a symlink." >&2
elif [[ -L $link && -e $link && $(realpath -m -- "$link") != "$skill_dir" ]]; then
# A live link to some other skill is the user's choice; only a dangling
# one, or one already pointing here, gets replaced.
echo "Leaving $link alone: it points elsewhere." >&2
else
mkdir -p -- "$dir"
ln -sfn -- "$skill_dir" "$link"
fi
done
76 changes: 76 additions & 0 deletions bin/omarchy-install-ai-cua
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/bin/bash

# omarchy:summary=Install Cua Driver, its agent skills, and the Cua Hyprland plugin
# omarchy:requires-sudo=true

set -euo pipefail

if (( EUID == 0 )); then
echo "Run this command as your desktop user, without sudo." >&2
exit 1
fi

echo "Installing Cua Driver..."
omarchy-pkg-add cua-driver-bin

# The driver takes its native Wayland backend only when this reaches the
# process serving the calls. default/hypr/envs.lua exports it to every session
# from the next login; this hands it to the one already running.
systemctl --user set-environment CUA_DRIVER_RS_ENABLE_WAYLAND=1 2>/dev/null || true
dbus-update-activation-environment CUA_DRIVER_RS_ENABLE_WAYLAND=1 2>/dev/null || true

# The skill pack is what teaches an agent the driver's snapshot-then-act loop:
# the driver fetches the release matching its own version and links it into
# every agent skills directory that exists, and Omarchy's companion adds the
# package and Hyprland specifics upstream cannot know. A skill is read into
# every session of every agent on the machine, so it is offered rather than
# imposed. Without a terminal to ask in, the driver alone is the answer.
skills_installed=false
if [[ -t 0 ]]; then
echo ""
echo "Cua ships skills that teach Claude Code, Codex, Hermes, and the other"
echo "agents on this machine how to drive apps through the driver."
if gum confirm "Install the Cua agent skills?"; then
echo "Installing the Cua agent skills..."
if cua-driver skills install; then
omarchy-agent-skill-link "$OMARCHY_PATH/default/agents/optional-skills/cua-omarchy"
skills_installed=true
else
echo "The Cua skills did not install. Run 'cua-driver skills install' later to try again." >&2
fi
fi
fi

# The Hyprland plugin gives the driver a compositor route for input. It is
# built against one exact Hyprland release, so it can be absent from the
# channel or present but unresolvable against the Hyprland that is installed
# (edge moves first), and neither undoes the driver install above. -Sp resolves
# the transaction without root, so the mismatch is named instead of surfacing
# as a failed sudo pacman run. Installing the plugin loads nothing: activation
# is a deliberate step, omarchy-toggle-cua-input, because it enables compositor input.
plugin_installed=false
echo ""
if ! pacman -Si cua-hyprland-plugin &>/dev/null; then
echo "The Cua Hyprland plugin is not available in your package channel; Cua Driver works without it."
elif ! pacman -Sp cua-hyprland-plugin &>/dev/null; then
pinned=$(pacman -Si cua-hyprland-plugin | grep -o 'hyprland=[^ ]*' | head -1)
echo "The Cua Hyprland plugin is built for ${pinned:-another Hyprland release} and this machine has hyprland=$(pacman -Q hyprland | cut -d' ' -f2); skipping it. Cua Driver works without it."
else
echo "Installing the Cua Hyprland plugin..."
if omarchy-pkg-add cua-hyprland-plugin; then
plugin_installed=true
else
echo "The Cua Hyprland plugin did not install. Cua Driver works without it." >&2
fi
fi

echo ""
echo "Cua Driver has been installed."
if [[ $skills_installed == true ]]; then
echo "Agents pick up the Cua skills in their next session."
fi
if [[ $plugin_installed == true ]]; then
echo "Trigger > Toggle > Cua Input enables the plugin without changing your keyboard settings."
echo "After a plugin update, log out and back in before enabling it. The plugin pins the exact"
echo "Hyprland release, so updates that move Hyprland wait until it is removed."
fi
44 changes: 44 additions & 0 deletions bin/omarchy-remove-ai-cua
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/bin/bash

# omarchy:summary=Remove Cua Driver, the Cua Hyprland plugin, and the agent skills they 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

# The driver knows which agent directories it linked its skill pack into, so
# it takes those links back before the package providing the command goes;
# --all removes the local copy too. A machine that never installed the skills
# has nothing to undo, and the driver says so without failing.
if omarchy-cmd-present cua-driver; then
cua-driver skills uninstall --all
fi
omarchy-agent-skill-link "$OMARCHY_PATH/default/agents/optional-skills/cua-omarchy" --remove

# The flag goes even when the plugin package went first: a mapped module keeps
# reading it until logout. The toggle tells the user if Hyprland did not take it.
if omarchy-hyprland-toggle-enabled cua-input; then
omarchy-toggle-cua-input off || true
fi

plugin_removed=false
if omarchy-pkg-present cua-hyprland-plugin; then
# The module itself stays mapped for the rest of the session, as the package requires.
omarchy-pkg-drop cua-hyprland-plugin
plugin_removed=true
fi
omarchy-pkg-drop cua-driver-bin

# Installation and telemetry identity, update checks, and the daemon's socket
# and logs: nothing here is the user's work. Recordings land wherever the
# recording was started, which is none of these.
rm -rf \
"$HOME/.cua-driver" \
"$HOME/.config/cua" \
"$HOME/.cache/cua-driver"

echo ""
echo "Cua Driver has been removed."
if [[ $plugin_removed == true ]]; then
echo "A loaded Hyprland plugin stays in memory until this session ends."
fi
198 changes: 198 additions & 0 deletions bin/omarchy-toggle-cua-input
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
#!/bin/bash

# omarchy:summary=Toggle the Cua Hyprland plugin's input route
# omarchy:args=[on|off|toggle|--status|--load]
# omarchy:examples=omarchy toggle cua-input | omarchy toggle cua-input off | omarchy-toggle-cua-input --status

# Activation checks the package and running module without replacing a mapped
# plugin. Turning input off leaves the module loaded until the session ends.

set -euo pipefail

FLAG=cua-input
FLAG_FILE="$HOME/.local/state/omarchy/toggles/hypr/$FLAG.lua"
MODULE=/usr/lib/cua/hyprland/cua-hyprland-plugin.so
KIT=/usr/share/cua-hyprland-plugin
MIN_VERSION=0.32.0-3
# nf-md-robot_excited, escaped so this file reads without a Nerd Font.
readonly GLYPH=$'\U000f16a4'

usage() {
echo "Usage: omarchy-toggle-cua-input [on|off|toggle|--status|--load]" >&2
exit 1
}

notify() {
omarchy-notification-send -g "$GLYPH" "$@" || true
}

# Removes the flag, then switches the live option off: a reload that kept the old
# settings, as a config syntax error does, still answers ok. Off only once both took.
flag_off() {
local live
omarchy-hyprland-toggle "$FLAG" off >/dev/null || true
live=$(hyprctl eval 'if hl.get_config("plugin.cua.enabled") ~= nil then hl.config({ plugin = { cua = { enabled = false } } }) end' 2>/dev/null)
[[ $live == ok && ! -e $FLAG_FILE ]]
}

refuse() {
local title="Cua input stays off" message=$1
if omarchy-hyprland-toggle-enabled "$FLAG" && ! flag_off; then
title="Cua input may still be on"
message+=" Hyprland did not take the change; log out and back in to turn it off."
fi
notify "$title" "$message"
echo "$message" >&2
exit 1
}

plugin_loaded() {
hyprctl -j plugin list 2>/dev/null | jq -e 'map(select(.name == "cua-hyprland-plugin")) | length > 0' >/dev/null
}

# A module that does not report these predates the installed package: it is still mapped from before an update.
module_current() {
hyprctl -j cua:status 2>/dev/null |
jq -e '.keyboard_layout_independent == true and .foreground_numlock_compatible == true and has("ime_popup_guard")' >/dev/null
}

# Without it, fcitx5 restarting while input is on can crash Hyprland 0.56.2.
ime_guard_active() {
hyprctl -j cua:status 2>/dev/null | jq -e '.ime_popup_guard == true' >/dev/null
}

package_current() {
local version comparison
version=$(pacman -Q cua-hyprland-plugin | cut -d' ' -f2) || return 1
comparison=$(vercmp "$version" "$MIN_VERSION") || return 1
(( comparison >= 0 ))
}

input_ready() {
hyprctl -j cua:status 2>/dev/null | jq -e '.configured == true and .transport.ready == true' >/dev/null
}

# The package's consumer check: the installed module against the running
# compositor and runtime, needing no compiler. It also wants the digest of the
# kit provenance it was built from; pacman verified that file when it
# installed it, so the digest is read from the file rather than pinned here,
# where it would go stale with every requalified profile.
compatible() {
local digest
digest=$(sha256sum "$KIT/KIT-PROVENANCE.json" | cut -d' ' -f1)
python3 "$KIT/profile_verify.py" --kit "$KIT" --kit-sha256 "$digest" --consumer "$MODULE" >/dev/null 2>&1
}

# Hyprland reloads its configuration when a plugin loads, so an enabled flag is
# held at enabled = false until the module passes; nocreat keeps a concurrent off.
hold_flag() {
sed 's/enabled = true/enabled = false/' "$OMARCHY_PATH/default/hypr/toggles/$FLAG.lua" |
dd of="$FLAG_FILE" conv=nocreat status=none 2>/dev/null && hyprctl reload >/dev/null
}

load_plugin() {
if ! plugin_loaded; then
hyprctl plugin load "$MODULE" >/dev/null
fi
}

print_status() {
local enabled=false loaded=false ready=false
omarchy-hyprland-toggle-enabled "$FLAG" && enabled=true
plugin_loaded && loaded=true
input_ready && ready=true
jq -cn --argjson enabled "$enabled" --argjson loaded "$loaded" --argjson ready "$ready" \
'{enabled:$enabled, loaded:$loaded, ready:$ready}'
}

turn_on() {
# Land the flag held off before anything else: an off while the checks run
# removes it, and the restore below never creates it again.
mkdir -p "$(dirname "$FLAG_FILE")"
sed 's/enabled = true/enabled = false/' "$OMARCHY_PATH/default/hypr/toggles/$FLAG.lua" >"$FLAG_FILE" ||
refuse "Cua input's configuration could not be written."
hyprctl reload >/dev/null || refuse "Hyprland could not reload its configuration."

omarchy-pkg-present cua-hyprland-plugin ||
refuse "The Cua Hyprland plugin is not installed; Install > AI > Cua adds it."

package_current || refuse "Update cua-hyprland-plugin to $MIN_VERSION or newer before turning Cua Input on."

compatible || refuse "The Cua Hyprland plugin does not match the running Hyprland; update it and log out and back in."
load_plugin || refuse "The Cua Hyprland plugin could not load; check the session logs."
if ! module_current; then
refuse "Log out and back in to use the updated Cua plugin, then turn Cua Input on again."
fi
ime_guard_active || refuse "The Cua plugin could not install its input-method crash guard; hyprctl -j cua:status has the details."

if ! dd if="$OMARCHY_PATH/default/hypr/toggles/$FLAG.lua" of="$FLAG_FILE" conv=nocreat status=none 2>/dev/null; then
[[ -e $FLAG_FILE ]] && refuse "Cua input's configuration could not be restored; turn it on again."
exit 0
fi
hyprctl reload >/dev/null || refuse "Hyprland could not reload its configuration."

if input_ready; then
notify "Cua input on" "Your keyboard layout and remaps stay unchanged."
else
notify "Cua plugin loaded, input not ready" "hyprctl -j cua:status has the details."
fi
}

turn_off() {
if ! flag_off; then
notify "Cua input may still be on" "Hyprland did not take the change; log out and back in to turn it off."
exit 1
fi
notify "Cua input off" "The plugin stays loaded until you log out."
Comment on lines +141 to +146

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Failed reload leaves input active

If hyprctl reload fails while turning Cua Input off, the flag has already been deleted, but the running compositor can retain the enabled setting. This command exits before notifying the user. The refusal path also ignores a failed off reload, so it can say input is off while it remains active.

Knowledge Base Used: Hyprland configuration and display control

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ea89af3. A shared flag_off removes the flag and then always sets the live option off with a guarded hyprctl eval, because a reload that keeps the old settings (a config syntax error) still answers ok. It counts as off only when the eval answered ok and the flag file is gone. off, every refusal and every failed session start go through it, and when it does not take they say input may still be on and to log out, instead of reporting it off. Tests cover a failed reload with the eval taking, both failing, a refusal, a session-start failure and an undeletable flag, each with a failing mutant. Run by Opus 5.5 in Claude Code; Codex XHigh reviewed the change and found nothing left to change.

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

}

# Session-start failures take the flag back and say whether that took.
startup_off() {
if flag_off; then
notify "Cua input turned off" "$1"
else
notify "Cua input may still be on" "$1 Hyprland did not take the change; log out and back in to turn it off."
fi
exit 0
}

# What the flag runs at session start.
load_at_start() {
omarchy-hyprland-toggle-enabled "$FLAG" || exit 0

if ! omarchy-pkg-present cua-hyprland-plugin || ! package_current || ! compatible; then
startup_off "Update the Cua Hyprland plugin and check that it matches the running Hyprland."
fi

if ! plugin_loaded && ! hold_flag; then
[[ -e $FLAG_FILE ]] && startup_off "Its configuration could not be held off while the plugin loaded; turn Cua Input on again."
exit 0
fi

load_plugin || startup_off "The Cua Hyprland plugin could not load; check the session logs."
module_current || startup_off "Log out and back in to use the updated plugin, then turn Cua Input on again."
ime_guard_active || startup_off "The Cua plugin could not install its input-method crash guard; hyprctl -j cua:status has the details."

# Refresh legacy keyboard overrides, but never recreate a flag removed by
# a concurrent off while verification ran. nocreat still truncates the file.
if dd if="$OMARCHY_PATH/default/hypr/toggles/$FLAG.lua" of="$FLAG_FILE" conv=nocreat status=none 2>/dev/null; then
hyprctl reload >/dev/null || startup_off "Hyprland could not reload its configuration; turn Cua Input on again."
elif [[ -e $FLAG_FILE ]]; then
startup_off "Its configuration could not be refreshed; turn Cua Input on again."
fi
}

case "${1:-toggle}" in
on) turn_on ;;
off) turn_off ;;
toggle)
if omarchy-hyprland-toggle-enabled "$FLAG"; then
turn_off
else
turn_on
fi
;;
--status | status) print_status ;;
--load) load_at_start ;;
*) usage ;;
esac
Loading