From 45e32c8c2d000ffee5488e16fb7601100a2c0511 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 15 Sep 2026 08:10:58 -0400 Subject: [PATCH] Set up browser integration when choosing Claude --- bin/omarchy-agent | 2 +- bin/omarchy-default-agent | 5 +++ bin/omarchy-install-chromium-claude | 43 +++++++++++++++++++ manual/17-ai.md | 2 + test/shell.d/chromium-claude-test.sh | 50 +++++++++++++++++++++++ test/shell.d/default-agent-test.sh | 22 +++++++++- test/shell.d/unowned-system-paths-test.sh | 7 +++- 7 files changed, 126 insertions(+), 5 deletions(-) create mode 100755 bin/omarchy-install-chromium-claude create mode 100644 test/shell.d/chromium-claude-test.sh diff --git a/bin/omarchy-agent b/bin/omarchy-agent index e35bcf04..23d0e2f3 100755 --- a/bin/omarchy-agent +++ b/bin/omarchy-agent @@ -75,7 +75,7 @@ crush) fi ;; claude) - command=(claude --permission-mode auto) + command=(claude --permission-mode auto --chrome) [[ -n ${prompt:-} ]] && command+=(-- "$prompt") ;; grok) diff --git a/bin/omarchy-default-agent b/bin/omarchy-default-agent index af93d863..8c14a4f2 100755 --- a/bin/omarchy-default-agent +++ b/bin/omarchy-default-agent @@ -86,6 +86,11 @@ if ! agent_install; then exit 1 fi +if [[ $agent == "claude" ]] && ! omarchy-install-chromium-claude; then + echo "Could not install the Claude browser extension" >&2 + exit 1 +fi + mkdir -p "$(dirname "$agent_file")" printf '%s\n' "$agent" >"$agent_file" diff --git a/bin/omarchy-install-chromium-claude b/bin/omarchy-install-chromium-claude new file mode 100755 index 00000000..df338271 --- /dev/null +++ b/bin/omarchy-install-chromium-claude @@ -0,0 +1,43 @@ +#!/bin/bash + +# omarchy:summary=Install the Claude extension for Chromium-based browsers +# omarchy:requires-sudo=true + +set -euo pipefail + +if (( EUID == 0 )); then + export PATH=/usr/bin:/bin +fi + +EXTENSION_ID="fcoeoabgfenejglbffodgkkbkcdhcgfn" +EXTENSION_JSON='{ "external_update_url": "https://clients2.google.com/service/update2/crx" }' +PACKAGED_PATH=/usr/bin/omarchy-install-chromium-claude + +# Brave and Brave Origin share Chromium's external extension directory. +# Seed all supported browsers, including those installed after choosing Claude. +EXTENSION_DIRS=( + /usr/share/chromium/extensions + /usr/share/google-chrome/extensions + /usr/share/microsoft-edge/extensions +) + +installed=true +for dir in "${EXTENSION_DIRS[@]}"; do + if [[ ! -f $dir/$EXTENSION_ID.json ]] || [[ $(cat "$dir/$EXTENSION_ID.json") != "$EXTENSION_JSON" ]]; then + installed=false + fi +done + +if [[ $installed == "true" ]]; then + exit 0 +fi + +if (( EUID == 0 )); then + for dir in "${EXTENSION_DIRS[@]}"; do + printf '%s\n' "$EXTENSION_JSON" | install -D -m 0644 /dev/stdin "$dir/$EXTENSION_ID.json" + done +elif [[ -t 0 ]]; then + exec sudo "$PACKAGED_PATH" +else + exec pkexec "$PACKAGED_PATH" +fi diff --git a/manual/17-ai.md b/manual/17-ai.md index 8c3d48b0..7f76d481 100644 --- a/manual/17-ai.md +++ b/manual/17-ai.md @@ -26,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. +Choosing Claude also sets up its browser extension for Chromium, Chrome, Brave, Brave Origin, and Edge, and enables browser integration when launched through `omarchy agent`. The extension setup applies to all users and may ask for your system password; cancelling leaves your previous default agent selected. Restart your browser, enable the extension if prompted, and sign in to Claude to finish connecting it. Run `/chrome` in Claude to check the connection. Firefox and Zen do not support this extension. If Claude was already your default before this setup was added, select it again to install the extension. + [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. diff --git a/test/shell.d/chromium-claude-test.sh b/test/shell.d/chromium-claude-test.sh new file mode 100644 index 00000000..5d8c61ef --- /dev/null +++ b/test/shell.d/chromium-claude-test.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +set -euo pipefail + +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" + +# Exercise the privileged installer without writing to the host's /usr/share. +if ! command -v bwrap >/dev/null || ! bwrap --ro-bind / / --unshare-user --uid 0 --gid 0 true 2>/dev/null; then + pass "user namespaces unavailable; skipping isolated Claude extension installation" + exit 0 +fi + +test_tmp=$(mktemp -d) +trap 'rm -rf "$test_tmp"' EXIT +mkdir -p "$test_tmp/share" "$test_tmp/bin" +installer="$ROOT/bin/omarchy-install-chromium-claude" +extension_id=fcoeoabgfenejglbffodgkkbkcdhcgfn +sandbox=(bwrap --ro-bind / / --bind "$test_tmp" "$test_tmp" --dev /dev --proc /proc --unshare-user) + +"${sandbox[@]}" --uid 0 --gid 0 --bind "$test_tmp/share" /usr/share bash "$installer" +for browser in chromium google-chrome microsoft-edge; do + file="$test_tmp/share/$browser/extensions/$extension_id.json" + jq -e '.external_update_url == "https://clients2.google.com/service/update2/crx"' "$file" >/dev/null || + fail "$browser registers the official Claude Web Store extension" + [[ $(stat -c '%a' "$file") == "644" ]] || fail "$browser extension registration is readable" +done +pass "Claude extension installer registers all supported browser families" + +cat >"$test_tmp/bin/pkexec" <<'SH' +#!/bin/bash +printf '%s\n' "$@" >"$AUTH_LOG" +exit 42 +SH +chmod +x "$test_tmp/bin/pkexec" +export AUTH_LOG="$test_tmp/auth-log" +export PATH="$test_tmp/bin:$PATH" + +# A read-only /usr/share and a failing auth stub prove that a repeated run +# neither rewrites the files nor requests authentication. +"${sandbox[@]}" --uid 1000 --gid 1000 --ro-bind "$test_tmp/share" /usr/share bash "$installer" "$mock_bin/omarchy-install-chromium-claude" <<'SH' +#!/bin/bash +echo claude-extension >>"$OMARCHY_TEST_STUB_LOG" +[[ ${OMARCHY_TEST_EXTENSION_FAIL:-false} != "true" ]] +SH + cat >"$mock_bin/omarchy-notification-send" <<'SH' #!/bin/bash printf '%s\0' "$@" >>"$OMARCHY_TEST_NOTIFICATION_HISTORY" @@ -406,9 +412,16 @@ declare -A expected_packages=( for selection in "${!expected_agents[@]}"; do expected=${expected_agents[$selection]} : >"$agent_open_log" + : >"$stub_log" OMARCHY_TEST_AGENT_INSTALLED=true omarchy-default-agent "$selection" [[ $(omarchy-default-agent) == $expected ]] || fail "default agent canonicalizes $selection" + if [[ $expected == "claude" ]]; then + grep -qx claude-extension "$stub_log" || fail "Claude selection installs the browser extension" + else + [[ ! -s $stub_log ]] || fail "other agents do not install the Claude extension" + fi + mapfile -d '' -t mise_args <"$mise_log" [[ ${mise_args[0]} == "use" && ${mise_args[1]} == "-g" ]] || fail "default agent installs $selection globally through mise" @@ -427,6 +440,11 @@ pass "default agent selects and opens every supported provider and alias" pass "default agent stores its selection in Omarchy user config" OMARCHY_TEST_AGENT_INSTALLED=true omarchy-default-agent pi +if OMARCHY_TEST_AGENT_INSTALLED=true OMARCHY_TEST_EXTENSION_FAIL=true omarchy-default-agent claude >"$test_tmp/extension-failure" 2>&1; then + fail "Claude selection fails when browser extension installation fails" +fi +[[ $(omarchy-default-agent) == "pi" ]] || fail "extension installation failure preserves the default agent" +pass "extension installation failure preserves the default agent" : >"$notification_history" : >"$agent_open_log" : >"$terminal_log" @@ -644,7 +662,7 @@ assert_launch pi pi "Review this project" assert_launch omp omp --auto-approve -- "Review this project" 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 claude claude --permission-mode auto --chrome -- "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" @@ -672,7 +690,7 @@ assert_bypass pi pi assert_bypass omp omp --auto-approve assert_bypass opencode opencode --auto assert_bypass ori ori code -assert_bypass claude claude --permission-mode auto +assert_bypass claude claude --permission-mode auto --chrome assert_bypass codex codex --approve-for-me assert_bypass muse muse --approval-mode never assert_bypass crush crush --yolo diff --git a/test/shell.d/unowned-system-paths-test.sh b/test/shell.d/unowned-system-paths-test.sh index e2450619..efe940b4 100755 --- a/test/shell.d/unowned-system-paths-test.sh +++ b/test/shell.d/unowned-system-paths-test.sh @@ -31,10 +31,13 @@ allowed = { # them so the hook does not exist where it does not apply. "/usr/lib/systemd/system-sleep", # Written through a variable, so the scan below cannot see them at the point - # they are written. Both drop configuration into another project's tree rather - # than Omarchy's, which is why neither is a candidate for omarchy-settings. + # they are written. These drop configuration into another project's tree + # rather than Omarchy's and are not candidates for omarchy-settings. "/usr/share/chromium/extensions", "/usr/lib/firefox/distribution", + # Claude's extension is registered only when the user selects Claude. + "/usr/share/google-chrome/extensions", + "/usr/share/microsoft-edge/extensions", # Static content that belongs in omarchy-settings. It cannot move there in the # same release that first ships omarchy-update-system-pkgs-when-conflicted: the # upgrade carrying the handler is the one that would hit the conflict, and the