From 9502b81f3ba9d873c683b87aab01d2063ec0c4cc Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 12 Aug 2026 17:56:19 +0200 Subject: [PATCH] Reshape the agent launcher into omarchy agent (#6757) * Reshape the agent launcher into omarchy agent omarchy-launch-agent becomes omarchy-agent, with prompts on omarchy-agent-prompt rather than the bare route: `omarchy agent` is both a command and a group, so a positional prompt there would shadow any subcommand under it. The launcher takes flags only and points at `omarchy agent prompt` when handed one. Every agent window now launches under a fixed org.omarchy.agent app-id instead of omarchy-launch-tui's default of org.omarchy., so one rule floats them all whichever agent is default. Omarchy also stops picking an agent for you. omarchy-default-agent prints nothing until one is chosen, leaving every entry under Setup > Defaults > Agent unchecked, and a first-run invitation offers to take you there. * Wordsmith * Cover the agent routes and the invitation The route split is the point of the change, so exercise `omarchy agent`, `omarchy agent prompt`, and a rejected positional prompt through the router rather than only the binaries behind them. The invitation gets the same treatment as the Voxtype and fingerprint ones: it notifies once, opens the agent defaults menu, and leaves both the notification and the marker alone for anyone who already chose an agent. * Offer the agent choice from the keybinding Super + Shift + Ctrl + A now runs `omarchy-agent --pick`, which opens Setup > Defaults > Agent when nothing is chosen yet. A keypress that writes to stderr and opens nothing just looks broken. * Reach existing installs with the agent invitation first-run installs the invitation hook, and existing accounts marked it complete long ago, so they would never see it -- while being the accounts most likely to need it, since the old getter returned opencode implicitly and most have no agent recorded at all. Post-update hooks run later in the same update, so the invitation arrives without waiting for another one. * Say what the Defaults submenus set Setup > Defaults lists Agent, Browser, Terminal, Editor, but the header inside each repeated the same bare word, which reads as a category rather than a setting -- and says nothing at all when the menu is summoned straight into it. The list keeps its short labels; the headers now name the setting. --- bin/{omarchy-launch-agent => omarchy-agent} | 53 +++++++--- bin/omarchy-agent-prompt | 23 +++++ bin/omarchy-default-agent | 8 +- bin/omarchy-provision-first-run | 2 + default/bash/aliases | 2 +- default/hypr/apps/agent.lua | 3 + default/hypr/bindings/utilities.lua | 2 +- default/omarchy/omarchy-menu.jsonc | 8 +- install/user/first-run/setup-agent.hook | 11 +++ migrations/1786549201.sh | 10 ++ test/shell.d/agent-invitation-test.sh | 61 ++++++++++++ test/shell.d/default-agent-test.sh | 102 +++++++++++++++----- 12 files changed, 240 insertions(+), 45 deletions(-) rename bin/{omarchy-launch-agent => omarchy-agent} (57%) create mode 100755 bin/omarchy-agent-prompt create mode 100644 default/hypr/apps/agent.lua create mode 100644 install/user/first-run/setup-agent.hook create mode 100644 migrations/1786549201.sh create mode 100644 test/shell.d/agent-invitation-test.sh diff --git a/bin/omarchy-launch-agent b/bin/omarchy-agent similarity index 57% rename from bin/omarchy-launch-agent rename to bin/omarchy-agent index 1a8af067..cb5cbd05 100755 --- a/bin/omarchy-launch-agent +++ b/bin/omarchy-agent @@ -1,15 +1,35 @@ #!/bin/bash # omarchy:summary=Launch the default coding agent in a terminal -# omarchy:args=[--inline] [prompt...] -# omarchy:examples=omarchy launch agent | omarchy launch agent "Review this project" +# omarchy:args=[--inline] [--pick] +# omarchy:examples=omarchy agent | omarchy agent --inline -if [[ ${1:-} == "--inline" ]]; then - inline=true - shift -else - inline=false -fi +inline=false +pick=false + +# Flags only. A bare prompt would shadow the subcommands under this group, so +# prompts go through omarchy-agent-prompt, which passes one here as --prompt. +while (($#)); do + case "$1" in + --inline) + inline=true + shift + ;; + --pick) + pick=true + shift + ;; + --prompt) + prompt=${2:?--prompt needs a value} + shift 2 + ;; + *) + echo "Unexpected argument: $1" >&2 + echo "To pass a prompt: omarchy agent prompt \"$*\"" >&2 + exit 1 + ;; + esac +done # Agents refuse to remember trust for $HOME, so launches from the keybinding or # menu start in the work directory instead of re-asking on every session. @@ -17,13 +37,18 @@ fi agent=$(omarchy-default-agent) -if omarchy-cmd-missing "$agent"; then - echo "$agent is not installed. Choose an installed agent with: omarchy default agent " >&2 +# Omarchy ships without a default, so there is nothing to launch until one is +# picked. --pick offers the choice instead of the error, which is what the +# keybinding wants: a keypress that opens nothing explains nothing. +if [[ -z $agent ]]; then + [[ $pick == "true" ]] && exec omarchy-menu summon setup.default.agent + echo "Choose default agent with: omarchy default agent " >&2 exit 1 fi -if (($# > 0)); then - prompt="$*" +if omarchy-cmd-missing "$agent"; then + echo "$agent is not installed. Choose an installed agent with: omarchy default agent " >&2 + exit 1 fi # Agents launched from the keybinding or menu run unattended, so each one starts @@ -74,5 +99,7 @@ esac if [[ $inline == "true" ]]; then exec "${command[@]}" else - exec omarchy-launch-tui "${command[@]}" + # A fixed app-id rather than the default org.omarchy., so every agent + # window shares one class for default/hypr/apps/agent.lua to float. + exec omarchy-launch-tui --app-id=org.omarchy.agent "${command[@]}" fi diff --git a/bin/omarchy-agent-prompt b/bin/omarchy-agent-prompt new file mode 100755 index 00000000..f5024b2f --- /dev/null +++ b/bin/omarchy-agent-prompt @@ -0,0 +1,23 @@ +#!/bin/bash + +# omarchy:summary=Launch the default coding agent with a prompt +# omarchy:args=[--inline] +# omarchy:examples=omarchy agent prompt "Review this project" + +# Prompts live here rather than on `omarchy agent`, where a bare prompt would +# shadow the subcommands under that group. + +set -euo pipefail + +inline=() +if [[ ${1:-} == "--inline" ]]; then + inline=(--inline) + shift +fi + +if (($# == 0)); then + echo "Usage: omarchy agent prompt [--inline] " >&2 + exit 1 +fi + +exec omarchy-agent "${inline[@]}" --prompt "$*" diff --git a/bin/omarchy-default-agent b/bin/omarchy-default-agent index 493522b5..f018b688 100755 --- a/bin/omarchy-default-agent +++ b/bin/omarchy-default-agent @@ -17,7 +17,9 @@ if (($# == 0)); then read -r agent <"$agent_file" fi - [[ -n $agent ]] && echo "$agent" || echo "opencode" + # Silent when unset rather than defaulting: Omarchy picks no agent for you, so + # the menu leaves every entry unchecked until one is chosen. + [[ -n ${agent:-} ]] && echo "$agent" exit 0 fi @@ -57,7 +59,7 @@ printf '%s\n' "$agent" >"$agent_file" if [[ $installing == "true" ]]; then printf '\033[2J\033[3J\033[H' - exec omarchy-launch-agent --inline + exec omarchy-agent --inline else - exec omarchy-launch-agent + exec omarchy-agent fi diff --git a/bin/omarchy-provision-first-run b/bin/omarchy-provision-first-run index 76eb0d78..11c2eb88 100755 --- a/bin/omarchy-provision-first-run +++ b/bin/omarchy-provision-first-run @@ -72,6 +72,8 @@ run_first_run_step "install Voxtype post-update hook" \ omarchy-hook-install post-update "$OMARCHY_PATH/install/user/first-run/install-voxtype.hook" run_first_run_step "install fingerprint setup post-update hook" \ omarchy-hook-install post-update "$OMARCHY_PATH/install/user/first-run/setup-fingerprint.hook" +run_first_run_step "install agent setup post-update hook" \ + omarchy-hook-install post-update "$OMARCHY_PATH/install/user/first-run/setup-agent.hook" run_first_run_step "enable user systemd units" \ bash "$OMARCHY_PATH/install/user/first-run/enable-user-units.sh" diff --git a/default/bash/aliases b/default/bash/aliases index e9630a24..38a3e2d6 100644 --- a/default/bash/aliases +++ b/default/bash/aliases @@ -43,7 +43,7 @@ alias ...='cd ../..' alias ....='cd ../../..' # Tools -alias a='omarchy-launch-agent --inline' +alias a='omarchy-agent --inline' alias c='opencode --auto' alias cx='printf "\033[2J\033[3J\033[H" && claude --permission-mode bypassPermissions' alias cy='codex -s danger-full-access -a never' diff --git a/default/hypr/apps/agent.lua b/default/hypr/apps/agent.lua new file mode 100644 index 00000000..18775342 --- /dev/null +++ b/default/hypr/apps/agent.lua @@ -0,0 +1,3 @@ +-- omarchy-agent gives every agent terminal the same app-id, so this +-- floats the keybinding, the menu, and a crash diagnosis alike. +o.window("org\\.omarchy\\.agent", { float = true, center = true, size = { 1200, 800 } }) diff --git a/default/hypr/bindings/utilities.lua b/default/hypr/bindings/utilities.lua index 820e7824..406b773f 100644 --- a/default/hypr/bindings/utilities.lua +++ b/default/hypr/bindings/utilities.lua @@ -93,7 +93,7 @@ o.bind("SUPER + CTRL + ALT + T", "Show time", "omarchy-notification-time") o.bind("SUPER + CTRL + ALT + B", "Show battery remaining", "omarchy-notification-battery") o.bind("SUPER + CTRL + ALT + W", "Toggle weather", "omarchy-notification-weather") -o.bind("SUPER + SHIFT + CTRL + A", "Agent", "omarchy-launch-agent") +o.bind("SUPER + SHIFT + CTRL + A", "Agent", "omarchy-agent --pick") o.bind("SUPER + CTRL + A", "Audio", "omarchy-shell shell toggle omarchy.audio") o.bind("SUPER + CTRL + B", "Bluetooth", "omarchy-shell shell toggle omarchy.bluetooth") o.bind("SUPER + CTRL + D", "Display", "omarchy-shell shell toggle omarchy.monitor") diff --git a/default/omarchy/omarchy-menu.jsonc b/default/omarchy/omarchy-menu.jsonc index 719607d0..5a094798 100644 --- a/default/omarchy/omarchy-menu.jsonc +++ b/default/omarchy/omarchy-menu.jsonc @@ -128,7 +128,7 @@ "setup.network.dns.custom": {"icon":"","label":"Custom","checked":"[[ \"$(omarchy-dns)\" == \"Custom\" ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-dns Custom'"}, "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"}, + "setup.default.agent": {"icon":"󰚩","label":"Agent","title":"Default Agent"}, "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"}, @@ -138,7 +138,7 @@ "setup.default.agent.omp": {"icon":"","iconFont":"omarchy","label":"omp","checked":"[[ \"$(omarchy-default-agent)\" == \"omp\" ]]","action":"omarchy-default-agent omp"}, "setup.default.agent.opencode": {"icon":"","iconFont":"omarchy","label":"OpenCode","checked":"[[ \"$(omarchy-default-agent)\" == \"opencode\" ]]","action":"omarchy-default-agent opencode"}, "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"}, + "setup.default.browser": {"icon":"","label":"Browser","title":"Default Browser"}, "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"}, @@ -146,12 +146,12 @@ "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"}, + "setup.default.terminal": {"icon":"","label":"Terminal","title":"Default Terminal"}, "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"}, + "setup.default.editor": {"icon":"","label":"Editor","title":"Default Editor"}, "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":"","label":"Cursor","when":"omarchy-cmd-present cursor","checked":"[[ \"$(omarchy-default-editor)\" == \"cursor\" ]]","action":"omarchy-default-editor cursor"}, diff --git a/install/user/first-run/setup-agent.hook b/install/user/first-run/setup-agent.hook new file mode 100644 index 00000000..d69c710c --- /dev/null +++ b/install/user/first-run/setup-agent.hook @@ -0,0 +1,11 @@ +#!/bin/bash + +set -e + +# Omarchy ships no default agent, so invite once rather than picking one. An +# agent already chosen means the invitation has nothing to offer. +if [[ -z $(omarchy-default-agent) ]] && omarchy-done ensure agent-setup-invitation; then + omarchy-notification-send -u critical -g 󰚩 "Set your default agent" \ + "Let your favorite agent help with Omarchy." \ + --exec "omarchy menu summon setup.default.agent" +fi diff --git a/migrations/1786549201.sh b/migrations/1786549201.sh new file mode 100644 index 00000000..8d285c89 --- /dev/null +++ b/migrations/1786549201.sh @@ -0,0 +1,10 @@ +echo "Invite existing installs to pick a default agent" + +# The invitation is installed by first-run, which existing accounts have already +# marked complete, so they would never receive it. They are also the accounts +# most likely to need it: the old getter returned opencode implicitly, so most +# have no agent recorded at all. +# +# Post-update hooks run later in this same update, so the invitation appears +# without waiting for another one. The hook decides whether to notify. +omarchy-hook-install post-update "$OMARCHY_PATH/install/user/first-run/setup-agent.hook" diff --git a/test/shell.d/agent-invitation-test.sh b/test/shell.d/agent-invitation-test.sh new file mode 100644 index 00000000..11de87d9 --- /dev/null +++ b/test/shell.d/agent-invitation-test.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +source "$(dirname "$0")/base-test.sh" + +test_home=$(mktemp -d) +test_bin=$(mktemp -d) +log_file=$(mktemp) +hook_path="$test_home/.config/omarchy/hooks/post-update.d/setup-agent.hook" + +cleanup() { + rm -rf "$test_home" "$test_bin" + rm -f "$log_file" +} +trap cleanup EXIT + +mkdir -p "$(dirname "$hook_path")" + +cat >"$test_bin/omarchy-notification-send" <<'EOF' +#!/bin/bash +echo notification >>"$TEST_LOG" +while (($# > 0)); do + [[ $1 == "--exec" ]] && echo "exec:$2" >>"$TEST_LOG" + shift +done +EOF +chmod +x "$test_bin/omarchy-notification-send" + +run_invitation_hook() { + cp "$ROOT/install/user/first-run/setup-agent.hook" "$hook_path" + HOME="$test_home" PATH="$test_bin:$ROOT/bin:$PATH" TEST_LOG="$log_file" bash "$hook_path" +} + +run_invitation_hook + +[[ -f $test_home/.local/state/omarchy/done/agent-setup-invitation ]] || fail "agent invitation records completion" +[[ -f $hook_path ]] || fail "agent invitation keeps its hook installed" +[[ $(grep -c '^notification$' "$log_file") -eq 1 ]] || fail "agent invitation sends one notification" +grep -qx 'exec:omarchy menu summon setup.default.agent' "$log_file" || + fail "agent invitation opens the agent defaults menu" + +run_invitation_hook +[[ $(grep -c '^notification$' "$log_file") -eq 1 ]] || fail "completed agent invitation does not notify again" + +pass "agent invitation only runs once" + +# Someone who already chose an agent has nothing to be invited to, and must not +# burn the marker either -- otherwise clearing the choice later leaves them with +# no invitation and no default. +fresh_home=$(mktemp -d) +mkdir -p "$fresh_home/.config/omarchy/defaults" +printf 'claude\n' >"$fresh_home/.config/omarchy/defaults/agent" +: >"$log_file" +cp "$ROOT/install/user/first-run/setup-agent.hook" "$hook_path" +HOME="$fresh_home" PATH="$test_bin:$ROOT/bin:$PATH" TEST_LOG="$log_file" bash "$hook_path" + +[[ ! -s $log_file ]] || fail "agent invitation stays quiet when a default is already set" +[[ ! -f $fresh_home/.local/state/omarchy/done/agent-setup-invitation ]] || + fail "agent invitation leaves its marker unset when a default is already set" +rm -rf "$fresh_home" + +pass "agent invitation skips anyone who already chose an agent" diff --git a/test/shell.d/default-agent-test.sh b/test/shell.d/default-agent-test.sh index 3b03e2f3..302c7f13 100644 --- a/test/shell.d/default-agent-test.sh +++ b/test/shell.d/default-agent-test.sh @@ -18,6 +18,7 @@ mise_log="$test_tmp/mise" mise_history="$test_tmp/mise-history" stub_log="$test_tmp/stubs" terminal_log="$test_tmp/terminal" +menu_log="$test_tmp/menu" mkdir -p "$mock_bin" "$test_home" cat >"$mock_bin/omarchy-notification-send" <<'SH' @@ -63,6 +64,11 @@ fi [[ ${OMARCHY_TEST_MISE_FAIL:-false} != "true" ]] SH +cat >"$mock_bin/omarchy-menu" <<'SH' +#!/bin/bash +printf '%s\0' "$@" >"$OMARCHY_TEST_AGENT_MENU_LOG" +SH + cat >"$mock_bin/omarchy-test-noop" <<'SH' #!/bin/bash exit 0 @@ -84,6 +90,7 @@ export OMARCHY_TEST_MISE_LOG="$mise_log" export OMARCHY_TEST_MISE_HISTORY="$mise_history" export OMARCHY_TEST_STUB_LOG="$stub_log" export OMARCHY_TEST_AGENT_TERMINAL_LOG="$terminal_log" +export OMARCHY_TEST_AGENT_MENU_LOG="$menu_log" grok_package="npm:@xai-official/grok" omp_package="github:can1357/oh-my-pi" @@ -140,30 +147,44 @@ for command in omp grok crush; do done pass "Remove Preinstalls deletes every optional agent lazy stub" -[[ $(omarchy-default-agent) == "opencode" ]] || fail "default agent falls back to OpenCode" -pass "default agent falls back to OpenCode" +[[ -z $(omarchy-default-agent) ]] || fail "default agent is unset until one is chosen" +pass "default agent is unset until one is chosen" -omarchy-launch-agent -mapfile -d '' -t launch_args <"$launch_log" -[[ ${launch_args[*]} == "opencode --auto" ]] || - fail "agent launcher falls back to OpenCode before a default is selected" -pass "agent launcher falls back to OpenCode before a default is selected" +: >"$launch_log" +if omarchy-agent >"$test_tmp/no-agent-output" 2>&1; then + fail "agent launcher refuses to launch without a default" +fi +grep -Fq "Choose default agent with" "$test_tmp/no-agent-output" || + fail "agent launcher explains that no default is set" +[[ ! -s $launch_log ]] || fail "agent launcher starts nothing without a default" +pass "agent launcher refuses to launch without a default" + +# The keybinding uses --pick, where an error on stderr nobody sees would make +# the keypress look broken. It offers the choice instead. +: >"$launch_log" +: >"$menu_log" +omarchy-agent --pick +mapfile -d '' -t menu_args <"$menu_log" +[[ ${menu_args[*]} == "summon setup.default.agent" ]] || + fail "--pick opens the agent defaults menu when none is set" +[[ ! -s $launch_log ]] || fail "--pick starts nothing when no agent is set" +pass "--pick opens the agent defaults menu when none is set" source "$ROOT/default/bash/aliases" -[[ $(alias a) == "alias a='omarchy-launch-agent --inline'" ]] || +[[ $(alias a) == "alias a='omarchy-agent --inline'" ]] || fail "terminal alias launches the default agent inline" pass "terminal alias launches the default agent inline" -grep -Fq 'o.bind("SUPER + SHIFT + CTRL + A", "Agent", "omarchy-launch-agent")' \ +grep -Fq 'o.bind("SUPER + SHIFT + CTRL + A", "Agent", "omarchy-agent --pick")' \ "$ROOT/default/hypr/bindings/utilities.lua" || fail "agent launcher has a keyboard shortcut" pass "agent launcher has a keyboard shortcut" -cat >"$mock_bin/omarchy-launch-agent" <<'SH' +cat >"$mock_bin/omarchy-agent" <<'SH' #!/bin/bash -printf '%s\0' omarchy-launch-agent "$@" >"$OMARCHY_TEST_AGENT_OPEN_LOG" +printf '%s\0' omarchy-agent "$@" >"$OMARCHY_TEST_AGENT_OPEN_LOG" SH -chmod +x "$mock_bin/omarchy-launch-agent" +chmod +x "$mock_bin/omarchy-agent" hash -r declare -A expected_agents=( @@ -206,7 +227,7 @@ for selection in "${!expected_agents[@]}"; do fail "default agent installs $selection globally through mise" mapfile -d '' -t agent_open_args <"$agent_open_log" - [[ ${#agent_open_args[@]} == 1 && ${agent_open_args[0]} == "omarchy-launch-agent" ]] || + [[ ${#agent_open_args[@]} == 1 && ${agent_open_args[0]} == "omarchy-agent" ]] || fail "default agent opens $selection after selecting it" done pass "default agent selects and opens every supported provider and alias" @@ -235,7 +256,7 @@ mapfile -d '' -t mise_args <"$mise_log" [[ $(<"$test_tmp/install-output") == $'\033[2J\033[3J\033[H' ]] || fail "visible agent installation clears its terminal before opening the agent" mapfile -d '' -t agent_open_args <"$agent_open_log" -[[ ${#agent_open_args[@]} == 2 && ${agent_open_args[0]} == "omarchy-launch-agent" && ${agent_open_args[1]} == "--inline" ]] || +[[ ${#agent_open_args[@]} == 2 && ${agent_open_args[0]} == "omarchy-agent" && ${agent_open_args[1]} == "--inline" ]] || fail "newly installed agent opens in the installation terminal" pass "missing agents install visibly and open in the same terminal" @@ -249,7 +270,7 @@ mapfile -d '' -t mise_args <"$mise_log" [[ ${mise_args[0]} == "use" && ${mise_args[1]} == "-g" && ${mise_args[2]} == "copilot" ]] || fail "default agent still activates an installed provider globally through mise" mapfile -d '' -t agent_open_args <"$agent_open_log" -[[ ${#agent_open_args[@]} == 1 && ${agent_open_args[0]} == "omarchy-launch-agent" ]] || +[[ ${#agent_open_args[@]} == 1 && ${agent_open_args[0]} == "omarchy-agent" ]] || fail "installed agent opens in a new terminal after selection" pass "installed agents select and open without notifications" @@ -287,14 +308,16 @@ grep -F "Could not set Codex as the default coding agent" "$test_tmp/setup-failu [[ ! -s $agent_open_log ]] || fail "failed activation does not open an agent" pass "default agent reports mise failures without notifications" -rm "$mock_bin/omarchy-launch-agent" +rm "$mock_bin/omarchy-agent" hash -r assert_launched() { local agent=$1 local description=$2 shift 2 - local expected=("$@") + # Every agent window launches under the same app-id, whichever agent is + # default, so default/hypr/apps/agent.lua can float them all. + local expected=(--app-id=org.omarchy.agent "$@") mapfile -d '' -t actual <"$launch_log" @@ -312,7 +335,7 @@ assert_launch() { shift printf '%s\n' "$agent" >"$agent_file" - omarchy-launch-agent "Review this" project + omarchy-agent-prompt "Review this" project assert_launched "$agent" "forwards the interactive prompt" "$@" } @@ -321,7 +344,7 @@ assert_bypass() { shift printf '%s\n' "$agent" >"$agent_file" - omarchy-launch-agent + omarchy-agent assert_launched "$agent" "skips permission prompts" "$@" } @@ -348,20 +371,53 @@ assert_bypass copilot copilot --allow-all pass "agent launcher skips permission prompts for every supported agent" printf '%s\n' "opencode" >"$agent_file" -omarchy-launch-agent +omarchy-agent mapfile -d '' -t launch_args <"$launch_log" -[[ ${launch_args[*]} == "opencode --auto" ]] || +[[ ${launch_args[*]} == "--app-id=org.omarchy.agent opencode --auto" ]] || fail "agent launcher starts the selected agent without an initial prompt" pass "agent launcher starts the selected agent without an initial prompt" -omarchy-launch-agent --inline "Review this project" +omarchy-agent-prompt --inline "Review this project" mapfile -d '' -t inline_args <"$inline_log" [[ ${inline_args[*]} == "opencode --auto --prompt Review this project" ]] || fail "inline agent launcher runs in the current terminal" pass "inline agent launcher runs in the current terminal" +# The prompt route exists so the router can tell a prompt from a subcommand, so +# cover the public routes and not only the binaries behind them. +: >"$launch_log" +omarchy agent +mapfile -d '' -t launch_args <"$launch_log" +[[ ${launch_args[*]} == "--app-id=org.omarchy.agent opencode --auto" ]] || + fail "omarchy agent routes to the launcher" + +# With an agent chosen there is nothing to pick, so the keybinding launches. +: >"$launch_log" +: >"$menu_log" +omarchy-agent --pick +mapfile -d '' -t launch_args <"$launch_log" +[[ ${launch_args[*]} == "--app-id=org.omarchy.agent opencode --auto" ]] || + fail "--pick launches once an agent is chosen" +[[ ! -s $menu_log ]] || fail "--pick opens no menu once an agent is chosen" +pass "--pick launches once an agent is chosen" + +: >"$launch_log" +omarchy agent prompt "Review this project" +mapfile -d '' -t launch_args <"$launch_log" +[[ ${launch_args[*]} == "--app-id=org.omarchy.agent opencode --auto --prompt Review this project" ]] || + fail "omarchy agent prompt routes the prompt to the launcher" + +: >"$launch_log" +if omarchy agent Review this project >"$test_tmp/positional-output" 2>&1; then + fail "omarchy agent rejects a positional prompt" +fi +grep -F "omarchy agent prompt" "$test_tmp/positional-output" >/dev/null || + fail "omarchy agent points a positional prompt at the prompt route" +[[ ! -s $launch_log ]] || fail "omarchy agent starts nothing for a positional prompt" +pass "omarchy agent keeps prompts on the prompt route" + printf '%s\n' "missing" >"$agent_file" -if OMARCHY_TEST_MISSING_COMMAND=missing omarchy-launch-agent >"$test_tmp/missing-output" 2>&1; then +if OMARCHY_TEST_MISSING_COMMAND=missing omarchy-agent >"$test_tmp/missing-output" 2>&1; then fail "agent launcher rejects a missing default command" fi grep -F "missing is not installed" "$test_tmp/missing-output" >/dev/null ||