diff --git a/bin/omarchy-agent b/bin/omarchy-agent index 3705eefa..e35bcf04 100755 --- a/bin/omarchy-agent +++ b/bin/omarchy-agent @@ -110,6 +110,11 @@ hermes) 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") diff --git a/bin/omarchy-default-agent b/bin/omarchy-default-agent index e79100e7..af93d863 100755 --- a/bin/omarchy-default-agent +++ b/bin/omarchy-default-agent @@ -1,7 +1,7 @@ #!/bin/bash # omarchy:summary=Set and launch the default coding agent -# omarchy:args=[pi|omp|opencode|ori|claude|codex|grok|openclaw|agy|hermes|copilot|crush|cursor-agent] +# omarchy:args=[pi|omp|opencode|ori|claude|codex|grok|openclaw|agy|hermes|copilot|crush|cursor-agent|muse] # omarchy:examples=omarchy default agent | omarchy default agent codex | omarchy default agent claude installing=false @@ -36,9 +36,14 @@ openclaw) agent="openclaw"; name="OpenClaw"; agent_installer="omarchy-install-op agy | antigravity | antigravity-cli | gemini | gemini-cli) agent="agy"; name="Antigravity"; agent_package="antigravity-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 " + echo "Usage: omarchy-default-agent " exit 1 ;; esac diff --git a/bin/omarchy-remove-preinstalls b/bin/omarchy-remove-preinstalls index 5da7a4e6..bfb8fe34 100755 --- a/bin/omarchy-remove-preinstalls +++ b/bin/omarchy-remove-preinstalls @@ -24,6 +24,12 @@ if gum confirm "Are you sure you want to remove all preinstalled web apps, TUI w 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 diff --git a/default/omarchy/omarchy-menu.jsonc b/default/omarchy/omarchy-menu.jsonc index a81b4baa..0e590652 100644 --- a/default/omarchy/omarchy-menu.jsonc +++ b/default/omarchy/omarchy-menu.jsonc @@ -143,6 +143,7 @@ "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.grok": {"icon":"","iconFont":"omarchy","label":"Grok","checked":"[[ \"$(omarchy-default-agent)\" == \"grok\" ]]","action":"omarchy-default-agent grok"}, "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"}, diff --git a/install/user/mise.sh b/install/user/mise.sh index ea118b54..b5c7884b 100644 --- a/install/user/mise.sh +++ b/install/user/mise.sh @@ -21,3 +21,6 @@ omarchy-mise-install github:OpenRouterLabs/ori-releases ori # omarchy-provision-user -- the default browser, the mailto handler and the # finalize-user marker all come after it. omarchy-install-hermes-cli || true +if omarchy-cmd-missing muse; then + omarchy-mise-install "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]" muse +fi diff --git a/manual/17-ai.md b/manual/17-ai.md index 156c0912..8911b974 100644 --- a/manual/17-ai.md +++ b/manual/17-ai.md @@ -15,6 +15,7 @@ Omarchy treats AI coding agents as first-class citizens, but it doesn't pick a f | `omp` | [Oh My Pi](https://github.com/can1357/oh-my-pi) | | `ori` | [Ori](https://openrouter.ai/docs/guides/ori/harness), OpenRouter's harness | | `hermes` | [Hermes](https://hermes-agent.nousresearch.com/), Nous Research's agent | +| `muse` | [Muse Code](https://dev.meta.ai), Meta's coding agent | | `cursor-agent` | [Cursor CLI](https://cursor.com/cli) | `ori` is the odd one out: it runs the other harnesses against OpenRouter's whole model catalog, so `ori claude`, `ori codex`, or `ori opencode` start those agents on whichever model you point them at, and `ori code` is Ori's own agent. @@ -25,6 +26,8 @@ To wrap an additional CLI the same way, run `omarchy-mise-install [com Pick your default agent with `omarchy default agent ` or under _Setup > Defaults > Agent_ in the Omarchy Menu (`Super + Space`). If the agent isn't installed yet, picking it installs it first. A fresh Omarchy will invite you to make this choice with a one-time notification. +[Muse Code](https://dev.meta.ai) — Meta's `muse` — uses a preinstalled mise stub like the other agents. Picking it as the default installs Meta's official launcher through mise's HTTP backend. The launcher verifies and updates the native binary for your machine. + Once you've chosen, `Super + Shift + Ctrl + A` launches the default agent in a dedicated terminal window (or brings up the picker if you haven't chosen yet). You can also launch it straight into a task with `omarchy agent prompt "Review this project"`. Agents launched this way run unattended in their respective don't-stop-to-ask modes, so be ready for them to actually do things! And since agents refuse to remember trust for your home directory, launches from `$HOME` start in `~/Work` instead. There are terminal shortcuts too: `a` runs the default agent inline in the current terminal, while `c`, `cx`, and `cy` start OpenCode, Claude Code, and Codex directly (again in their auto-approving modes). Theme changes sync to the agents as well: Claude Code, Pi, OpenCode, and Hermes (once Hermes Desktop is installed) all follow along when you switch the Omarchy theme. diff --git a/migrations/1788724825.sh b/migrations/1788724825.sh new file mode 100644 index 00000000..cc76dc0f --- /dev/null +++ b/migrations/1788724825.sh @@ -0,0 +1,5 @@ +echo "Install Muse Code via mise wrapper" + +if omarchy-cmd-missing muse && [[ ! -f $HOME/.local/state/omarchy/preinstalls-removed ]]; then + omarchy-mise-install "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]" muse +fi diff --git a/test/shell.d/default-agent-test.sh b/test/shell.d/default-agent-test.sh index 26fdad38..ea65b973 100644 --- a/test/shell.d/default-agent-test.sh +++ b/test/shell.d/default-agent-test.sh @@ -19,6 +19,7 @@ mise_history="$test_tmp/mise-history" stub_log="$test_tmp/stubs" terminal_log="$test_tmp/terminal" menu_log="$test_tmp/menu" +muse_login_log="$test_tmp/muse-login" mkdir -p "$mock_bin" "$test_home" cat >"$mock_bin/omarchy-notification-send" <<'SH' @@ -69,6 +70,22 @@ cat >"$mock_bin/omarchy-menu" <<'SH' printf '%s\0' "$@" >"$OMARCHY_TEST_AGENT_MENU_LOG" SH +cat >"$mock_bin/omarchy-pkg-add" <<'SH' +#!/bin/bash +echo "Muse must install through mise" >&2 +exit 1 +SH +ln -s omarchy-pkg-add "$mock_bin/omarchy-pkg-aur-add" + +cat >"$mock_bin/muse" <<'SH' +#!/bin/bash +if [[ ${1:-} == "login" ]]; then + printf 'muse %s\n' "$*" >>"$OMARCHY_TEST_MUSE_LOGIN_LOG" +else + printf '%s\0' muse "$@" >"$OMARCHY_TEST_AGENT_INLINE_LOG" +fi +SH + cat >"$mock_bin/omarchy-test-noop" <<'SH' #!/bin/bash exit 0 @@ -91,6 +108,7 @@ 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" +export OMARCHY_TEST_MUSE_LOGIN_LOG="$muse_login_log" export OMARCHY_PATH="$ROOT" grok_package="npm:@xai-official/grok" @@ -99,6 +117,7 @@ crush_package="crush" agy_package="antigravity-cli" ori_package="github:OpenRouterLabs/ori-releases" cursor_agent_package="cursor-agent" +muse_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]" assert_lazy_stub() { local package=$1 @@ -118,6 +137,7 @@ assert_lazy_stub "$omp_package" omp assert_lazy_stub "$crush_package" crush assert_lazy_stub "$ori_package" ori assert_lazy_stub "$cursor_agent_package" cursor-agent +assert_lazy_stub "$muse_package" muse pass "custom agent lazy stubs preserve their mise packages" OMARCHY_TEST_MISSING_COMMAND=cursor-agent source "$ROOT/install/user/mise.sh" @@ -127,12 +147,29 @@ grep -Fx "$cursor_agent_package" "$stub_log" >/dev/null || fail "user setup crea grep -Fx "$omp_package omp" "$stub_log" >/dev/null || fail "user setup creates the Oh My Pi lazy stub" grep -Fx "$crush_package" "$stub_log" >/dev/null || fail "user setup creates the Crush lazy stub" grep -Fx "$ori_package ori" "$stub_log" >/dev/null || fail "user setup creates the Ori lazy stub" +OMARCHY_TEST_MISSING_COMMAND=muse source "$ROOT/install/user/mise.sh" +grep -Fx "$muse_package muse" "$stub_log" >/dev/null || fail "user setup creates the Muse lazy stub" pass "user setup creates the custom agent lazy stubs" : >"$stub_log" source "$ROOT/install/user/mise.sh" grep -Fx "$cursor_agent_package" "$stub_log" >/dev/null && fail "user setup replaces an existing cursor-agent command" pass "user setup keeps an existing Cursor CLI install" +grep -Fx "$muse_package muse" "$stub_log" >/dev/null && fail "user setup replaces an existing Muse command" + +: >"$stub_log" +OMARCHY_TEST_MISSING_COMMAND=muse source "$ROOT/migrations/1788724825.sh" >/dev/null +grep -Fx "$muse_package muse" "$stub_log" >/dev/null || fail "Muse migration creates its lazy stub" +: >"$stub_log" +source "$ROOT/migrations/1788724825.sh" >/dev/null +[[ ! -s $stub_log ]] || fail "Muse migration replaces an existing command" +mkdir -p "$test_home/.local/state/omarchy" +touch "$test_home/.local/state/omarchy/preinstalls-removed" +OMARCHY_TEST_MISSING_COMMAND=muse source "$ROOT/migrations/1788724825.sh" >/dev/null +[[ ! -s $stub_log ]] || fail "Muse migration ignores the preinstall opt-out" +rm "$test_home/.local/state/omarchy/preinstalls-removed" +pass "Muse migration preserves existing installs and the preinstall opt-out" + : >"$stub_log" source "$ROOT/migrations/1785617047.sh" >/dev/null @@ -260,9 +297,10 @@ rm "$test_home/.local/state/omarchy/preinstalls-removed" rm -f "$agent_file" pass "agent migrations install working wrappers without overriding the preinstall opt-out" +"$ROOT/bin/omarchy-mise-install" "$muse_package" muse touch "$test_home/.local/bin/agy" "$test_home/.local/bin/ori" omarchy-remove-preinstalls >/dev/null -for command in agy omp ori grok crush cursor-agent; do +for command in agy omp ori grok crush cursor-agent muse; do [[ ! -e $test_home/.local/bin/$command ]] || fail "Remove Preinstalls deletes the $command lazy stub" done pass "Remove Preinstalls deletes every optional agent lazy stub" @@ -275,6 +313,13 @@ omarchy-remove-preinstalls >/dev/null [[ -L $test_home/.local/bin/cursor-agent ]] || fail "Remove Preinstalls keeps an official Cursor CLI install" rm -f "$test_home/.local/bin/cursor-agent" "$test_home/.local/bin/cursor-agent.official" pass "Remove Preinstalls keeps an official Cursor CLI install" +printf '#!/bin/bash\necho user-muse\n' >"$test_home/.local/bin/muse" +chmod +x "$test_home/.local/bin/muse" +omarchy-remove-preinstalls >/dev/null +[[ $("$test_home/.local/bin/muse") == "user-muse" ]] || fail "Remove Preinstalls deletes a user-managed Muse" +rm "$test_home/.local/bin/muse" +pass "Remove Preinstalls keeps a user-managed Muse install" + [[ -z $(omarchy-default-agent) ]] || fail "default agent is unset until one is chosen" pass "default agent is unset until one is chosen" @@ -338,6 +383,9 @@ declare -A expected_agents=( [github-copilot]="copilot" [cursor]="cursor-agent" [cursor-agent]="cursor-agent" + [muse]="muse" + [muse-code]="muse" + [musecode]="muse" ) declare -A expected_packages=( @@ -352,6 +400,7 @@ declare -A expected_packages=( [agy]="$agy_package" [copilot]="copilot" [cursor-agent]="$cursor_agent_package" + [muse]="$muse_package" ) for selection in "${!expected_agents[@]}"; do @@ -361,8 +410,12 @@ for selection in "${!expected_agents[@]}"; do [[ $(omarchy-default-agent) == $expected ]] || fail "default agent canonicalizes $selection" mapfile -d '' -t mise_args <"$mise_log" - [[ ${mise_args[0]} == "use" && ${mise_args[1]} == "-g" && ${mise_args[2]} == ${expected_packages[$expected]} ]] || + [[ ${mise_args[0]} == "use" && ${mise_args[1]} == "-g" ]] || fail "default agent installs $selection globally through mise" + case ${mise_args[2]} in + "${expected_packages[$expected]}") ;; + *) fail "default agent preserves $selection backend options" ;; + esac mapfile -d '' -t agent_open_args <"$agent_open_log" [[ ${#agent_open_args[@]} == 1 && ${agent_open_args[0]} == "omarchy-agent" ]] || @@ -475,6 +528,76 @@ 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" +# Muse follows the shared mise installation and launch path. +: >"$notification_history" +: >"$agent_open_log" +: >"$terminal_log" +omarchy-default-agent muse +mapfile -d '' -t terminal_args <"$terminal_log" +[[ ${terminal_args[0]} == "omarchy-default-agent" && ${terminal_args[1]} == "--install" && ${terminal_args[2]} == "muse" ]] || + fail "missing Muse installation opens in a terminal" +[[ ! -s $notification_history ]] || fail "missing Muse installation skips notifications" +[[ ! -s $agent_open_log ]] || fail "missing Muse installation waits to open the agent" +[[ $(omarchy-default-agent) == "copilot" ]] || fail "missing Muse installation waits to change the selection" + +if OMARCHY_TEST_MISE_FAIL=true omarchy-default-agent --install muse >"$test_tmp/muse-install-failure-output" 2>&1; then + fail "missing Muse rejects a failed mise installation" +fi +[[ $(omarchy-default-agent) == "copilot" ]] || fail "failed Muse installation preserves the current default" +[[ ! -s $muse_login_log && ! -s $agent_open_log ]] || fail "failed Muse installation skips login and launch" +grep -F "Could not install Muse Code with mise" "$test_tmp/muse-install-failure-output" >/dev/null || + fail "failed Muse installation identifies mise" +pass "failed Muse mise installation preserves the selection and skips login" + +: >"$mise_history" +: >"$stub_log" +omarchy-default-agent --install muse >"$test_tmp/muse-install-output" +grep -Fx "use -g $muse_package" "$mise_history" >/dev/null || fail "visible Muse installation uses the HTTP backend" +[[ ! -s $stub_log ]] || fail "Muse selection recreates its preinstalled wrapper" +[[ ! -s $muse_login_log ]] || fail "Muse selection runs a separate login flow" +[[ $(omarchy-default-agent) == "muse" ]] || fail "visible Muse installation changes the selection" +mapfile -d '' -t agent_open_args <"$agent_open_log" +[[ ${#agent_open_args[@]} == 2 && ${agent_open_args[0]} == "omarchy-agent" && ${agent_open_args[1]} == "--inline" ]] || + fail "newly installed Muse opens in the installation terminal" +pass "Muse installs visibly through mise and opens directly" + +: >"$terminal_log" +: >"$muse_login_log" +: >"$agent_open_log" +OMARCHY_TEST_AGENT_INSTALLED=true omarchy-default-agent muse-code +[[ ! -s $terminal_log ]] || fail "installed Muse selection skips the terminal" +[[ ! -s $muse_login_log ]] || fail "installed Muse selection skips the login" +[[ $(omarchy-default-agent) == "muse" ]] || fail "default agent canonicalizes muse-code" +mapfile -d '' -t agent_open_args <"$agent_open_log" +[[ ${#agent_open_args[@]} == 1 && ${agent_open_args[0]} == "omarchy-agent" ]] || + fail "installed Muse opens in a new terminal after selection" +pass "installed Muse selects and opens directly" + +OMARCHY_TEST_AGENT_INSTALLED=true omarchy-default-agent pi +: >"$agent_open_log" +if OMARCHY_TEST_AGENT_INSTALLED=true OMARCHY_TEST_MISE_FAIL=true omarchy-default-agent musecode >"$test_tmp/muse-failure-output" 2>&1; then + fail "default agent rejects a failed Muse activation" +fi +[[ $(omarchy-default-agent) == "pi" ]] || fail "failed Muse activation preserves the current default agent" +grep -F "Could not set Muse Code as the default coding agent" "$test_tmp/muse-failure-output" >/dev/null || + fail "default agent reports a failed Muse activation" +[[ ! -s $agent_open_log ]] || fail "failed Muse activation does not open an agent" +pass "default agent reports Muse mise failures without changing the selection" + +# A manually installed launcher belongs to the user; selecting it must not +# install a second copy or replace it with the Omarchy wrapper. +printf '#!/bin/bash\necho user-muse\n' >"$test_home/.local/bin/muse" +chmod +x "$test_home/.local/bin/muse" +: >"$mise_history" +: >"$stub_log" +: >"$terminal_log" +omarchy-default-agent muse +[[ $(omarchy-default-agent) == "muse" ]] || fail "a user-installed Muse can be selected" +[[ ! -s $mise_history && ! -s $stub_log && ! -s $terminal_log ]] || fail "a user-installed Muse skips installation and wrapper creation" +[[ $("$test_home/.local/bin/muse") == "user-muse" ]] || fail "a user-installed Muse is preserved" +rm "$test_home/.local/bin/muse" +pass "selecting a user-installed Muse preserves its launcher" + rm "$mock_bin/omarchy-agent" hash -r @@ -523,6 +646,7 @@ assert_launch opencode opencode --auto --prompt "Review this project" assert_launch ori ori code --interactive --prompt "Review this project" assert_launch claude claude --permission-mode auto -- "Review this project" assert_launch codex codex --approve-for-me -- "Review this project" +assert_launch muse muse --approval-mode never -- "Review this project" assert_launch crush crush run "Review this project" assert_launch grok grok --permission-mode bypassPermissions -- "Review this project" assert_launch cursor-agent cursor-agent --yolo --trust agent -- "Review this project" @@ -531,6 +655,12 @@ assert_launch agy agy --dangerously-skip-permissions --prompt-interactive "Revie assert_launch copilot copilot --allow-all --interactive "Review this project" pass "agent launcher adapts initial prompts for every supported agent" +literal_muse_prompt=$'--disable-sandbox !Crash {$(touch must-not-run)}\ntrailing\\ ' +printf '%s\n' "muse" >"$agent_file" +omarchy-agent-prompt "$literal_muse_prompt" +assert_launched muse "separates prompt text from options" muse --approval-mode never -- "$literal_muse_prompt" +pass "Muse receives option-like prompts as one literal argument" + literal_hermes_prompt=$' --help !Crash /quit {$(touch must-not-run)}\ntrailing\\ ' printf '%s\n' "hermes" >"$agent_file" omarchy-agent-prompt "$literal_hermes_prompt" @@ -544,6 +674,7 @@ assert_bypass opencode opencode --auto assert_bypass ori ori code assert_bypass claude claude --permission-mode auto assert_bypass codex codex --approve-for-me +assert_bypass muse muse --approval-mode never assert_bypass crush crush --yolo assert_bypass grok grok --permission-mode bypassPermissions assert_bypass cursor-agent cursor-agent --yolo --trust diff --git a/test/shell.d/menu-test.sh b/test/shell.d/menu-test.sh index 1365c3bf..b227bb10 100644 --- a/test/shell.d/menu-test.sh +++ b/test/shell.d/menu-test.sh @@ -228,7 +228,9 @@ const expectedAgents = { openclaw: { icon: '\ue90c', iconFont: 'omarchy', label: 'OpenClaw' }, copilot: { icon: '', label: 'Copilot' }, crush: { icon: '󰋑', label: 'Crush' }, + muse: { icon: '󰛤', label: 'Muse Code' }, 'cursor-agent': { icon: '\ue90d', iconFont: 'omarchy', label: 'Cursor CLI' }, + } assert( Object.entries(expectedAgents).every(([agent, expected]) => { @@ -241,13 +243,13 @@ assert( && !entry.when && entry.checked.includes(`== \"${agent}\"`) }), - 'menu exposes every mise-installable coding agent with its own glyph under Defaults > Agent' + 'menu exposes every supported coding agent with its own glyph under Defaults > Agent' ) assertDeepEqual( defaultItems .filter(item => item.parent === 'setup.default.agent') .map(item => item.label), - ['Antigravity', 'Claude', 'Codex', 'Copilot', 'Crush', 'Cursor CLI', 'Grok', 'Hermes', 'omp', 'OpenClaw', 'OpenCode', 'Ori', 'Pi'], + ['Antigravity', 'Claude', 'Codex', 'Copilot', 'Crush', 'Cursor CLI', 'Grok', 'Hermes', 'Muse Code', 'omp', 'OpenClaw', 'OpenCode', 'Ori', 'Pi'], 'menu sorts coding agents alphabetically' ) const expectedDefaults = {