From 57ea0b4cd0b2fb1a10aab001e0cfd14f031131f8 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Wed, 29 Jul 2026 19:39:55 -0700 Subject: [PATCH] Simplify bar plugin management (#6435) --- bin/omarchy-bar | 220 +++++++- bin/omarchy-bar-plugin | 503 ------------------ bin/omarchy-install-service-dropbox | 2 +- bin/omarchy-install-service-tailscale | 2 +- bin/omarchy-plugin-add | 26 +- bin/omarchy-plugin-catalog | 6 +- bin/omarchy-plugin-clone | 39 +- bin/omarchy-plugin-enable | 67 ++- bin/omarchy-plugin-remove | 22 +- bin/omarchy-remove-service-dropbox | 2 +- bin/omarchy-remove-service-tailscale | 2 +- default/omarchy-skill/SKILL.md | 2 +- docs/omarchy-shell.md | 7 +- shell/README.md | 14 +- shell/plugins/README.md | 8 +- shell/plugins/bar/README.md | 8 +- shell/plugins/model-usage/README.md | 10 +- shell/plugins/panels/tailscale/README.md | 2 +- shell/services/PluginRegistry.qml | 303 +++++++++-- shell/shell.qml | 40 +- test/shell.d/config-test.sh | 170 +++--- .../fixtures/plugin-registry/shell.qml | 137 ++++- test/shell.d/menu-test.sh | 2 +- test/shell.d/plugin-clone-test.sh | 78 +-- test/shell.d/plugin-enable-test.sh | 45 ++ test/shell.d/runtime-smoke-test.sh | 4 +- 26 files changed, 886 insertions(+), 835 deletions(-) delete mode 100755 bin/omarchy-bar-plugin create mode 100644 test/shell.d/plugin-enable-test.sh diff --git a/bin/omarchy-bar b/bin/omarchy-bar index 36c62092..bf2923ff 100755 --- a/bin/omarchy-bar +++ b/bin/omarchy-bar @@ -1,9 +1,9 @@ #!/bin/bash -# omarchy:summary=Set the active bar option, position, and transparency +# omarchy:summary=Configure the bar and its widget layout # omarchy:group=bar -# omarchy:args=use | reset | defaults | position | transparent -# omarchy:examples=omarchy bar use local.neon-bar | omarchy bar reset | omarchy bar defaults | omarchy bar position top | omarchy bar transparent true +# omarchy:args=use | reset | defaults | position | transparent | move [placement] | set [--json] [placement] +# omarchy:examples=omarchy bar use local.neon-bar | omarchy bar move omarchy.clock --section center --index 0 | omarchy bar set omarchy.clock format HH:mm set -euo pipefail @@ -18,15 +18,26 @@ Usage: omarchy bar [args...] defaults Restore the default bar and service widgets position Bar position transparent Bar transparency + move [placement] Move a widget within or between sections + set [--json] [placement] + Set a per-widget option -Bar widgets are added, moved, removed, and configured with 'omarchy bar plugin'. +Placement: + --section Target section + --index Target index + --before Insert before a widget + --after Insert after a widget + --from-section
Source section + --from-index Source index + +Enable and disable widgets with 'omarchy plugin enable' and +'omarchy plugin disable'. Examples: omarchy bar use local.neon-bar - omarchy bar reset - omarchy bar defaults - omarchy bar position top - omarchy bar transparent true + omarchy bar move omarchy.media left + omarchy bar move omarchy.clock --section center --index 0 + omarchy bar set omarchy.clock format HH:mm USAGE } @@ -38,6 +49,87 @@ bar_option_exists() { ' >/dev/null } +validate_section() { + [[ $1 =~ ^(left|center|right)$ ]] || fail "section must be left, center, or right" +} + +validate_index() { + [[ $1 =~ ^[0-9]+$ ]] || fail "index must be a non-negative integer" +} + +# --------------------------------------------------------------------- placement + +PLACEMENT_SECTION="" +PLACEMENT_INDEX="" +PLACEMENT_BEFORE="" +PLACEMENT_AFTER="" +PLACEMENT_FROM_SECTION="" +PLACEMENT_FROM_INDEX="" + +parse_placement() { + while (( $# > 0 )); do + case "$1" in + --section) + PLACEMENT_SECTION="${2:-}" + validate_section "$PLACEMENT_SECTION" + shift 2 + ;; + --index) + PLACEMENT_INDEX="${2:-}" + validate_index "$PLACEMENT_INDEX" + shift 2 + ;; + --before) + PLACEMENT_BEFORE="${2:-}" + [[ -n $PLACEMENT_BEFORE ]] || fail "--before requires a widget id" + shift 2 + ;; + --after) + PLACEMENT_AFTER="${2:-}" + [[ -n $PLACEMENT_AFTER ]] || fail "--after requires a widget id" + shift 2 + ;; + --from-section) + PLACEMENT_FROM_SECTION="${2:-}" + validate_section "$PLACEMENT_FROM_SECTION" + shift 2 + ;; + --from-index) + PLACEMENT_FROM_INDEX="${2:-}" + validate_index "$PLACEMENT_FROM_INDEX" + shift 2 + ;; + -h | --help) + usage + exit 0 + ;; + *) + fail "unknown option: $1" + ;; + esac + done + + [[ -z $PLACEMENT_BEFORE || -z $PLACEMENT_AFTER ]] || fail "use only one of --before or --after" +} + +placement_json() { + jq -cn \ + --arg section "$PLACEMENT_SECTION" \ + --arg index "$PLACEMENT_INDEX" \ + --arg before "$PLACEMENT_BEFORE" \ + --arg after "$PLACEMENT_AFTER" \ + --arg fromSection "$PLACEMENT_FROM_SECTION" \ + --arg fromIndex "$PLACEMENT_FROM_INDEX" ' + {} + + (if $section == "" then {} else {section: $section} end) + + (if $index == "" then {} else {index: ($index | tonumber)} end) + + (if $before == "" then {} else {before: $before} end) + + (if $after == "" then {} else {after: $after} end) + + (if $fromSection == "" then {} else {fromSection: $fromSection} end) + + (if $fromIndex == "" then {} else {fromIndex: ($fromIndex | tonumber)} end) + ' +} + # -------------------------------------------------------------------- commands cmd_use() { @@ -59,15 +151,48 @@ cmd_use() { cmd_defaults() { (( $# == 0 )) || fail "defaults does not take arguments" - # Restore the whole default bar, then re-add widgets for services the user - # actually has installed (they aren't in the shipped default layout). - commit "$NORMALIZE | .bar = \$defaults[0].bar" --slurpfile defaults "$DEFAULTS_FILE" - local service + + local catalog + local optional_widgets="[]" + local service widget + catalog=$(omarchy-plugin-catalog) for service in dropbox tailscale; do if "omarchy-installed-service-$service"; then - omarchy-bar-plugin add "omarchy.$service" + widget=$(jq -c --arg id "omarchy.$service" ' + map(select(.id == $id))[0] as $plugin + | { + id: $id, + section: ( + $plugin.barWidget.defaultSection // "center" + | if IN("left", "center", "right") then . else "center" end + ) + } + ' <<<"$catalog") + optional_widgets=$(jq -c --argjson widget "$widget" '. + [$widget]' <<<"$optional_widgets") fi done + + # This remains one file mutation so it also works during the headless + # Quattro upgrade and cannot race the shell's in-memory config. + commit "$NORMALIZE + | .bar = \$defaults[0].bar + | def entry_id: if type == \"object\" then (.id // \"\" | tostring) else tostring end; + def anchor_for(\$section): { left: \"omarchy.workspaces\", center: \"omarchy.weather\", right: \"omarchy.tray\" }[\$section]; + reduce \$widgets[] as \$widget (.; + .bar.layout.left = (.bar.layout.left | map(select(entry_id != \$widget.id))) + | .bar.layout.center = (.bar.layout.center | map(select(entry_id != \$widget.id))) + | .bar.layout.right = (.bar.layout.right | map(select(entry_id != \$widget.id))) + | (.bar.layout[\$widget.section] | map(entry_id) | index(anchor_for(\$widget.section))) as \$anchor + | (\$anchor | if . == null then (.bar.layout[\$widget.section] | length) else . + 1 end) as \$index + | .bar.layout[\$widget.section] = ( + .bar.layout[\$widget.section][0:\$index] + + [{id: \$widget.id}] + + .bar.layout[\$widget.section][\$index:] + ) + ) + " \ + --slurpfile defaults "$DEFAULTS_FILE" \ + --argjson widgets "$optional_widgets" echo "Restored the default Omarchy bar" } @@ -94,6 +219,67 @@ cmd_transparent() { fi } +cmd_move() { + local id="${1:-}" + [[ -n $id ]] || fail "move requires a widget id" + shift + + local positional_section="" + if (( $# > 0 )) && [[ $1 != --* ]]; then + positional_section="$1" + validate_section "$positional_section" + shift + fi + + parse_placement "$@" + [[ -z $positional_section || -z $PLACEMENT_SECTION ]] || + fail "specify a section positionally or with --section, not both" + [[ -z $positional_section || -z $PLACEMENT_INDEX ]] || + fail "specify a section positionally or use --index, not both" + [[ -z $positional_section || -z $PLACEMENT_BEFORE ]] || + fail "specify a section positionally or use --before, not both" + [[ -z $positional_section || -z $PLACEMENT_AFTER ]] || + fail "specify a section positionally or use --after, not both" + [[ -z $positional_section ]] || PLACEMENT_SECTION="$positional_section" + + local result + result=$(omarchy-shell shell moveBarWidget "$id" "$(placement_json)") + [[ $result == "ok" ]] || fail "$result" + echo "Moved $id" +} + +cmd_set() { + local id="${1:-}" + local key="${2:-}" + local value="${3:-}" + [[ -n $id ]] || fail "set requires a widget id" + [[ -n $key ]] || fail "set requires a setting key" + (( $# >= 3 )) || fail "set requires a value" + shift 3 + + local value_is_json="false" + if (( $# > 0 )) && [[ $1 == "--json" ]]; then + value_is_json="true" + shift + fi + parse_placement "$@" + [[ -z $PLACEMENT_BEFORE && -z $PLACEMENT_AFTER ]] || + fail "set does not accept --before or --after" + + local value_json + if [[ $value_is_json == "true" ]]; then + value_json=$(jq -cn --argjson value "$value" '$value') || + fail "invalid JSON value: $value" + else + value_json=$(jq -cn --arg value "$value" '$value') + fi + + local result + result=$(omarchy-shell shell setBarWidget "$id" "$key" "$value_json" "$(placement_json)") + [[ $result == "ok" ]] || fail "$result" + echo "Set $key on $id" +} + # --------------------------------------------------------------------- dispatch command="${1:-}" @@ -116,11 +302,11 @@ case "$command" in transparent) cmd_transparent "$@" ;; - plugin) - exec omarchy-bar-plugin "$@" + move) + cmd_move "$@" ;; - add | move | remove | rm | drop | set | replace) - fail "bar widgets are managed with: omarchy bar plugin $command ..." + set) + cmd_set "$@" ;; -h | --help | help | "") usage diff --git a/bin/omarchy-bar-plugin b/bin/omarchy-bar-plugin deleted file mode 100755 index 15da1ea5..00000000 --- a/bin/omarchy-bar-plugin +++ /dev/null @@ -1,503 +0,0 @@ -#!/bin/bash - -# omarchy:summary=Add, move, remove, and configure bar plugin widgets in the layout -# omarchy:group=bar -# omarchy:args=add [placement] | move [placement] | remove [placement] | set [--json] [placement] | replace -# omarchy:examples=omarchy bar plugin add omarchy.tailscale | omarchy bar plugin add omarchy.clock center | omarchy bar plugin move omarchy.media left | omarchy bar plugin move omarchy.clock --section center --index 0 | omarchy bar plugin remove omarchy.tailscale | omarchy bar plugin set omarchy.clock format HH:mm - -set -euo pipefail - -source omarchy-shell-config - -usage() { - cat < [args...] - - add [placement] Add a bar widget - move [placement] [--from-section S --from-index N] - Move a widget within/between sections - remove [placement] (alias: rm, drop) - Remove a widget - set [--json] [placement] - Set a per-widget option - replace Replace the first instance of a widget id - -Placement (for add/move/remove/set): - --section Target section - --index Insert/remove at index in the target section - --before Insert before the first matching widget - --after Insert after the first matching widget - --from-section
Source section (move/remove/set) - --from-index Source index (move/remove/set) - --duplicate add: allow a second instance of the widget - --all remove: remove every matching instance - -Without placement flags, 'add' inserts after the section anchor (workspaces on -left, weather on center, tray on right) and dedupes across sections; 'move' -preserves the source section; 'remove' drops the first match (or every match -with --all). - -Widget ids are listed by 'omarchy plugin list'. - -Examples: - omarchy bar plugin add omarchy.tailscale - omarchy bar plugin add omarchy.clock center - omarchy bar plugin move omarchy.media left - omarchy bar plugin move omarchy.clock --section center --index 0 - omarchy bar plugin remove omarchy.tailscale - omarchy bar plugin remove omarchy.tailscale --all - omarchy bar plugin set omarchy.clock format HH:mm - omarchy bar plugin set omarchy.indicators items '["Dnd","NightLight"]' --json -USAGE -} - -# jq defs used by the mutation pipelines. All `def`s come first so the pipeline -# that follows them stays valid jq; NORMALIZE adds its own shape helpers. -JQ_DEFS=' - def entry_id: if type == "object" then (.id // "" | tostring) else tostring end; - def anchor_for($section): { left: "omarchy.workspaces", center: "omarchy.weather", right: "omarchy.tray" }[$section]; - def find_all($id; $only): - [ ["left","center","right"][] as $section - | ((.bar.layout[$section] // []) | to_entries[]) - | select((($only == "" or $section == $only)) and ((.value | entry_id) == $id)) - | {section: $section, index: .key, entry: .value} ]; - def entry_at($section; $index): - if $index < 0 or $index >= (.bar.layout[$section] | length) then - error("no widget at " + $section + "[" + ($index | tostring) + "]") - else .bar.layout[$section][$index] end; - def resolve_target($defaultSection): - . as $root - | (if $before != "" or $after != "" then - ($root | find_all($before + $after; $targetSection) | .[0] - // error("could not find target widget " + ($before + $after) + ($targetSection | if . == "" then "" else " in " + . end))) - | .section as $section | (.index + (if $after == "" then 0 else 1 end)) as $index - | {section: $section, index: $index, anchor: false} - elif $targetSection != "" or $targetIndex != "" then - { section: ($targetSection | if . == "" then $defaultSection else . end), - index: (if $targetIndex == "" then null else ($targetIndex | tonumber) end), - anchor: false } - else - { section: $defaultSection, index: null, anchor: true } - end) - | .section as $section - | .index as $rawIndex - | (if $rawIndex == null then - if .anchor then - ($root.bar.layout[$section] | map(entry_id) | index(anchor_for($section))) as $anchorIndex - | if $anchorIndex == null then ($root.bar.layout[$section] | length) else ($anchorIndex + 1) end - else ($root.bar.layout[$section] | length) end - elif $rawIndex > ($root.bar.layout[$section] | length) then ($root.bar.layout[$section] | length) - else $rawIndex end) as $index - | {section: $section, index: $index}; - def resolve_source: - if $fromIndex != "" then - if $fromSection == "" then error("--from-index requires --from-section") - else - ($fromIndex | tonumber) as $index - | entry_at($fromSection; $index) as $entry - | if $widgetId != "" and ($entry | entry_id) != $widgetId then - error("widget at " + $fromSection + "[" + ($index | tostring) + "] is not " + $widgetId) - else {section: $fromSection, index: $index, entry: $entry} end - end - else - (find_all($widgetId; $fromSection) - | if length == 0 then - error("could not find widget " + $widgetId + ($fromSection | if . == "" then "" else " in " + . end)) - else .[0] end) - end; -' - -validate_section() { - [[ $1 =~ ^(left|center|right)$ ]] || fail "section must be left, center, or right" -} - -validate_index() { - [[ $1 =~ ^[0-9]+$ ]] || fail "index must be a non-negative integer" -} - -widget_is_known() { - omarchy-plugin-catalog | jq -e --arg id "$1" ' - any(.[]; (.kinds | index("bar-widget")) and .id == $id) - ' >/dev/null -} - -widget_default_section() { - omarchy-plugin-catalog | jq -r --arg id "$1" ' - (map(select(.id == $id))[0].barWidget.defaultSection // "center") as $section - | if ["left", "center", "right"] | index($section) then $section else "center" end - ' -} - -# --------------------------------------------------------------------- placement - -# Parse placement flags shared by add/move/remove/set. Sets the globals below. -PLACEMENT_SECTION="" -PLACEMENT_INDEX="" -PLACEMENT_BEFORE="" -PLACEMENT_AFTER="" -PLACEMENT_FROM_SECTION="" -PLACEMENT_FROM_INDEX="" -PLACEMENT_DUPLICATE="false" -PLACEMENT_ALL="false" - -parse_placement() { - while (( $# > 0 )); do - case "$1" in - --section) - PLACEMENT_SECTION="${2:-}" - validate_section "$PLACEMENT_SECTION" - shift 2 - ;; - --index) - PLACEMENT_INDEX="${2:-}" - validate_index "$PLACEMENT_INDEX" - shift 2 - ;; - --before) - PLACEMENT_BEFORE="${2:-}" - [[ -n $PLACEMENT_BEFORE ]] || fail "--before requires a widget id" - shift 2 - ;; - --after) - PLACEMENT_AFTER="${2:-}" - [[ -n $PLACEMENT_AFTER ]] || fail "--after requires a widget id" - shift 2 - ;; - --from-section) - PLACEMENT_FROM_SECTION="${2:-}" - validate_section "$PLACEMENT_FROM_SECTION" - shift 2 - ;; - --from-index) - PLACEMENT_FROM_INDEX="${2:-}" - validate_index "$PLACEMENT_FROM_INDEX" - shift 2 - ;; - --duplicate) - PLACEMENT_DUPLICATE="true" - shift - ;; - --all) - PLACEMENT_ALL="true" - shift - ;; - -h | --help) - usage - exit 0 - ;; - *) - fail "unknown option: $1" - ;; - esac - done - - [[ -z $PLACEMENT_BEFORE || -z $PLACEMENT_AFTER ]] || fail "use only one of --before or --after" -} - -# --------------------------------------------------------------------- commands - -cmd_add() { - local id="${1:-}" - [[ -n $id ]] || fail "add requires a widget id" - shift - - local positional_section="" - while (( $# > 0 )); do - if [[ $1 == --* ]]; then - break - fi - [[ -z $positional_section ]] || fail "unexpected argument: $1" - positional_section="$1" - shift - done - - parse_placement "$@" - [[ -z $positional_section || -z $PLACEMENT_SECTION ]] || fail "specify a section positionally or with --section, not both" - [[ -z $positional_section || -z $PLACEMENT_INDEX ]] || fail "specify a section positionally or use --index, not both" - [[ -z $positional_section || -z $PLACEMENT_BEFORE ]] || fail "specify a section positionally or use --before, not both" - [[ -z $positional_section || -z $PLACEMENT_AFTER ]] || fail "specify a section positionally or use --after, not both" - [[ -z $positional_section || ( $PLACEMENT_DUPLICATE == false ) ]] || fail "--duplicate needs explicit placement flags" - if [[ -n $positional_section ]]; then - validate_section "$positional_section" - PLACEMENT_SECTION="$positional_section" - fi - - widget_is_known "$id" || fail "$id is not a known bar widget; run 'omarchy plugin list' to see valid ids" - - local default_section="${PLACEMENT_SECTION:-$(widget_default_section "$id")}" - local explicit="false" - if [[ -n $PLACEMENT_INDEX || -n $PLACEMENT_BEFORE || -n $PLACEMENT_AFTER || $PLACEMENT_DUPLICATE == true ]]; then - explicit="true" - fi - - if [[ $explicit == "false" ]]; then - # Anchor-based dedupe insert: remove any existing instance across sections, - # then insert after the section anchor (workspaces/weather/tray) — or append - # when the anchor is absent. - commit "$JQ_DEFS $NORMALIZE - | .bar.layout.left = (.bar.layout.left | map(select(entry_id != \$id))) - | .bar.layout.center = (.bar.layout.center | map(select(entry_id != \$id))) - | .bar.layout.right = (.bar.layout.right | map(select(entry_id != \$id))) - | (.bar.layout[\$section] | map(entry_id) | index(anchor_for(\$section))) as \$anchorIndex - | .bar.layout[\$section] = ( - if \$anchorIndex == null then .bar.layout[\$section] + [{id: \$id}] - else .bar.layout[\$section][0:\$anchorIndex+1] + [{id: \$id}] + .bar.layout[\$section][\$anchorIndex+1:] end) - " \ - --arg id "$id" \ - --arg section "$default_section" - else - local prog - prog=$(cat < 0 )); do - if [[ $1 == --* ]]; then - break - fi - [[ -z $positional_section ]] || fail "unexpected argument: $1" - positional_section="$1" - shift - done - - parse_placement "$@" - [[ -z $positional_section || -z $PLACEMENT_SECTION ]] || fail "specify a section positionally or with --section, not both" - [[ -z $positional_section || -z $PLACEMENT_INDEX ]] || fail "specify a section positionally or use --index, not both" - [[ -z $positional_section || -z $PLACEMENT_BEFORE ]] || fail "specify a section positionally or use --before, not both" - [[ -z $positional_section || -z $PLACEMENT_AFTER ]] || fail "specify a section positionally or use --after, not both" - if [[ -n $positional_section ]]; then - validate_section "$positional_section" - PLACEMENT_SECTION="$positional_section" - fi - - local default_section="${PLACEMENT_SECTION:-}" - - # A bare section names the section, not the slot, so let it fall through to - # the same anchor placement 'add' uses. Passing it as an explicit target - # instead drops the widget on the far end of the row. - local target_section="$PLACEMENT_SECTION" - if [[ -z $PLACEMENT_INDEX && -z $PLACEMENT_BEFORE && -z $PLACEMENT_AFTER ]]; then - target_section="" - fi - - local prog - prog=$(cat < 0 )) && [[ $1 != --* ]]; then - id="$1" - shift - fi - parse_placement "$@" - - local prog - prog=$(cat <= 3 )) || fail "set requires a value" - shift 3 - local value_is_json="false" - while (( $# > 0 )); do - case "$1" in - --json) value_is_json="true" ;; - -h | --help) usage; exit 0 ;; - *) break ;; - esac - shift - done - parse_placement "$@" - - if [[ $value_is_json == "true" ]]; then - jq -n --argjson value "$value" empty >/dev/null 2>&1 || fail "invalid JSON value: $value" - fi - - local value_arg - if [[ $value_is_json == "true" ]]; then - value_arg=(--argjson value "$value") - else - value_arg=(--arg value "$value") - fi - - local prog - prog=$(cat < 0 )) && shift || true - -case "$command" in - add) - cmd_add "$@" - ;; - move) - cmd_move "$@" - ;; - remove | rm | drop) - cmd_remove "$@" - ;; - set) - cmd_set "$@" - ;; - replace) - cmd_replace "$@" - ;; - -h | --help | help | "") - usage - ;; - *) - fail "unknown command: $command" - ;; -esac diff --git a/bin/omarchy-install-service-dropbox b/bin/omarchy-install-service-dropbox index 169c0623..0ee07bfb 100755 --- a/bin/omarchy-install-service-dropbox +++ b/bin/omarchy-install-service-dropbox @@ -6,7 +6,7 @@ echo "Installing all dependencies..." omarchy-pkg-add dropbox dropbox-cli libappindicator-gtk3 python-gpgme nautilus-dropbox echo "Adding Dropbox to the bar..." -omarchy-bar-plugin add omarchy.dropbox +omarchy-plugin-enable omarchy.dropbox echo "Starting Dropbox..." uwsm-app -- dropbox-cli start &>/dev/null & diff --git a/bin/omarchy-install-service-tailscale b/bin/omarchy-install-service-tailscale index 58dd28b5..5bb304cf 100755 --- a/bin/omarchy-install-service-tailscale +++ b/bin/omarchy-install-service-tailscale @@ -17,6 +17,6 @@ echo -e "\nReceiving Taildrop files in $HOME/Downloads..." systemctl --user enable --now omarchy-tailscale-receive.service echo -e "\nAdding Tailscale to the bar..." -omarchy-bar-plugin add omarchy.tailscale +omarchy-plugin-enable omarchy.tailscale omarchy-webapp-install "Tailscale" "https://login.tailscale.com/admin/machines" https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/tailscale-light.png diff --git a/bin/omarchy-plugin-add b/bin/omarchy-plugin-add index 40167826..f0bdfca5 100755 --- a/bin/omarchy-plugin-add +++ b/bin/omarchy-plugin-add @@ -33,7 +33,9 @@ confirm() { fi } -place_bar_widget() { +ENABLE_PLACEMENT=() + +select_bar_widget_placement() { local id="$1" local section local default_section @@ -47,8 +49,7 @@ place_bar_widget() { section=$(printf '%s\n' left center right | gum choose --header="Place $id in which bar section?" --selected "$default_section") || return 0 [[ -n $section ]] || return 0 - omarchy-bar-plugin move "$id" --section "$section" >/dev/null && - echo "Placed $id in the $section section" + ENABLE_PLACEMENT=(--section "$section") } plugin_id_manifest() { @@ -151,20 +152,17 @@ if [[ -z $enable_after ]]; then fi if [[ $enable_after == true ]]; then + select_bar_widget_placement "$id" + discovered=0 for (( attempt = 0; attempt < 40; attempt++ )); do - result=$(omarchy-shell shell setPluginEnabled "$id" true) - [[ $result == "ok" ]] && break + if omarchy-plugin-list --json | jq -e --arg id "$id" 'any(.[]; .id == $id)' >/dev/null; then + discovered=1 + break + fi sleep 0.05 done - [[ $result == "ok" ]] || fail "plugin '$id' is not known" - if omarchy-plugin-catalog | jq -e --arg id "$id" ' - any(.[]; .id == $id and (.kinds | index("bar"))) - ' >/dev/null; then - echo "Now using $id as the bar" - else - echo "Enabled $id" - fi - place_bar_widget "$id" + (( discovered )) || fail "plugin '$id' is not known" + omarchy-plugin-enable "$id" "${ENABLE_PLACEMENT[@]}" else echo "Enable it later with: omarchy plugin enable $id" fi diff --git a/bin/omarchy-plugin-catalog b/bin/omarchy-plugin-catalog index 2ecb3a85..986a2654 100755 --- a/bin/omarchy-plugin-catalog +++ b/bin/omarchy-plugin-catalog @@ -7,10 +7,8 @@ # Walks $OMARCHY_PATH/shell/plugins and ~/.config/omarchy/plugins, reads every # manifest.json / *.manifest.json, and emits one JSON object per plugin with # computed fields (sourceDir, barWidgetPath, barPath, firstParty). This is the -# single source of truth that omarchy-bar (widget/option enumeration), -# omarchy-bar-plugin (add validation), and omarchy-plugin-clone (source -# enumeration) read, so the bar and plugin commands never re-implement -# manifest walking. +# single source of truth used by bar selection and plugin enable/clone, so +# those commands never re-implement manifest walking. set -o pipefail diff --git a/bin/omarchy-plugin-clone b/bin/omarchy-plugin-clone index d03eb2d3..e645ce69 100755 --- a/bin/omarchy-plugin-clone +++ b/bin/omarchy-plugin-clone @@ -79,36 +79,6 @@ update_manifest() { mv "$manifest.tmp" "$manifest" } -switch_to_clone() { - local source_id="$1" - local new_id="$2" - local is_bar="$3" - local is_bar_widget="$4" - local has_non_widget_kind="$5" - - if [[ $is_bar == "true" ]]; then - omarchy-bar use "$new_id" - elif [[ $is_bar_widget == "true" ]]; then - local shell_config - shell_config=$(omarchy-shell shell listShellConfig) - if jq -e --arg id "$source_id" ' - [ - .bar.layout.left[]?, - .bar.layout.center[]?, - .bar.layout.right[]? - ] | any(.[]; (if type == "object" then .id else . end) == $id) - ' <<<"$shell_config" >/dev/null; then - omarchy-bar-plugin replace "$source_id" "$new_id" - else - omarchy-bar-plugin add "$new_id" - fi - [[ $has_non_widget_kind == "false" ]] || omarchy-plugin-disable "$source_id" - else - omarchy-plugin-disable "$source_id" - omarchy-plugin-enable "$new_id" - fi -} - usage() { cat < @@ -145,14 +115,11 @@ source_info=$(omarchy-plugin-catalog | jq -r --arg id "$source_id" ' | [ .sourceDir, .manifestPath, - (.name // .id), - ((.kinds | index("bar")) != null), - ((.kinds | index("bar-widget")) != null), - (any(.kinds[]; . != "bar-widget")) + (.name // .id) ] | @tsv ') [[ -n $source_info ]] || fail "unknown built-in plugin: $source_id" -IFS=$'\t' read -r source_dir source_manifest source_name is_bar is_bar_widget has_non_widget_kind <<<"$source_info" +IFS=$'\t' read -r source_dir source_manifest source_name <<<"$source_info" new_id="local.${source_id#omarchy.}" display_name="My $source_name" @@ -182,7 +149,7 @@ for (( attempt = 0; attempt < 40; attempt++ )); do sleep 0.05 done (( discovered )) || fail "cloned plugin '$new_id' was not discovered" -switch_to_clone "$source_id" "$new_id" "$is_bar" "$is_bar_widget" "$has_non_widget_kind" +omarchy-plugin-enable "$new_id" >/dev/null clone_complete=1 omarchy-notification-send -g 󰐱 \ "Editing Cloned Plugin" \ diff --git a/bin/omarchy-plugin-enable b/bin/omarchy-plugin-enable index c4785a31..ece1283d 100755 --- a/bin/omarchy-plugin-enable +++ b/bin/omarchy-plugin-enable @@ -27,12 +27,69 @@ if (( $# > 0 )) && omarchy-plugin-catalog | jq -e --arg id "$id" ' fail "'$id' is a bar; it replaces the bar in use rather than taking a place in one" fi -result=$(omarchy-shell shell setPluginEnabled "$id" true) -[[ $result == "ok" ]] || - fail "plugin '$id' is not known; run: omarchy-shell shell rescanPlugins" +section="" +index="" +before="" +after="" +if (( $# > 0 )) && [[ $1 != --* ]]; then + section="$1" + shift +fi +while (( $# > 0 )); do + case "$1" in + --section) + [[ -z $section ]] || fail "specify a section positionally or with --section, not both" + section="${2:-}" + [[ -n $section ]] || fail "--section requires a section" + shift 2 + ;; + --index) + index="${2:-}" + [[ -n $index ]] || fail "--index requires an index" + shift 2 + ;; + --before) + before="${2:-}" + [[ -n $before ]] || fail "--before requires a widget id" + shift 2 + ;; + --after) + after="${2:-}" + [[ -n $after ]] || fail "--after requires a widget id" + shift 2 + ;; + *) + fail "unknown placement option: $1" + ;; + esac +done -if (( $# > 0 )); then - omarchy-bar-plugin move "$id" "$@" +[[ -z $section || $section =~ ^(left|center|right)$ ]] || + fail "section must be left, center, or right" +[[ -z $index || $index =~ ^[0-9]+$ ]] || + fail "index must be a non-negative integer" +[[ -z $before || -z $after ]] || fail "use only one of --before or --after" + +placement=$(jq -cn \ + --arg section "$section" \ + --arg index "$index" \ + --arg before "$before" \ + --arg after "$after" ' + {} + + (if $section == "" then {} else {section: $section} end) + + (if $index == "" then {} else {index: ($index | tonumber)} end) + + (if $before == "" then {} else {before: $before} end) + + (if $after == "" then {} else {after: $after} end) + ') + +result=$(omarchy-shell shell enablePlugin "$id" "$placement") +if [[ $result == "unknown" ]]; then + fail "plugin '$id' is not known; run: omarchy-shell shell rescanPlugins" +elif [[ $result != "ok" ]]; then + fail "$result" +fi + +if [[ $placement != "{}" ]]; then echo "Enabled and moved $id" elif omarchy-plugin-catalog | jq -e --arg id "$id" ' any(.[]; .id == $id and (.kinds | index("bar"))) diff --git a/bin/omarchy-plugin-remove b/bin/omarchy-plugin-remove index 0310accf..2e8591f7 100755 --- a/bin/omarchy-plugin-remove +++ b/bin/omarchy-plugin-remove @@ -80,7 +80,6 @@ if [[ -n $shell_plugins ]]; then fi cloned_from="" -restored_source=0 if [[ -f $target/manifest.json ]]; then cloned_from=$(jq -r '.omarchy.clonedFrom // empty' "$target/manifest.json") fi @@ -93,24 +92,7 @@ else confirm "Remove '$id'? The folder will be backed up." || fail "aborted" fi -if [[ -n $cloned_from ]]; then - is_bar=$(jq -r '(.kinds | index("bar")) != null' "$target/manifest.json") - is_bar_widget=$(jq -r '(.kinds | index("bar-widget")) != null' "$target/manifest.json") - has_non_widget_kind=$(jq -r 'any(.kinds[]; . != "bar-widget")' "$target/manifest.json") - - if [[ $is_bar == "true" && $was_enabled == "true" ]]; then - omarchy-bar use "$cloned_from" - restored_source=1 - elif [[ $is_bar_widget == "true" && $was_enabled == "true" ]]; then - omarchy-bar-plugin replace "$id" "$cloned_from" - [[ $has_non_widget_kind == "false" ]] || omarchy-plugin-enable "$cloned_from" - restored_source=1 - elif [[ $has_non_widget_kind == "true" ]]; then - [[ $was_enabled != "true" ]] || omarchy-shell shell setPluginEnabled "$id" false >/dev/null - omarchy-plugin-enable "$cloned_from" - restored_source=1 - fi -elif [[ $was_enabled == "true" ]]; then +if [[ $was_enabled == "true" ]]; then omarchy-shell shell setPluginEnabled "$id" false >/dev/null fi @@ -134,7 +116,7 @@ fi omarchy-shell shell rescanPlugins >/dev/null -if (( restored_source )); then +if [[ -n $cloned_from && $was_enabled == "true" ]]; then echo "Restored $cloned_from." elif [[ $was_enabled == "true" ]]; then echo "Plugin was enabled and was unloaded from omarchy-shell." diff --git a/bin/omarchy-remove-service-dropbox b/bin/omarchy-remove-service-dropbox index 0480017e..4c749095 100644 --- a/bin/omarchy-remove-service-dropbox +++ b/bin/omarchy-remove-service-dropbox @@ -4,7 +4,7 @@ # omarchy:requires-sudo=true dropbox-cli stop 2>/dev/null || true -omarchy-bar-plugin remove omarchy.dropbox +omarchy-plugin-disable omarchy.dropbox omarchy-pkg-drop dropbox dropbox-cli libappindicator-gtk3 python-gpgme nautilus-dropbox echo "" diff --git a/bin/omarchy-remove-service-tailscale b/bin/omarchy-remove-service-tailscale index adbe2103..814f4210 100644 --- a/bin/omarchy-remove-service-tailscale +++ b/bin/omarchy-remove-service-tailscale @@ -6,7 +6,7 @@ tailscale down 2>/dev/null || true systemctl --user disable --now omarchy-tailscale-receive.service 2>/dev/null || true sudo systemctl disable --now tailscaled.service 2>/dev/null || true -omarchy-bar-plugin remove omarchy.tailscale +omarchy-plugin-disable omarchy.tailscale omarchy-webapp-remove "Tailscale" 2>/dev/null || true omarchy-pkg-drop tailscale diff --git a/default/omarchy-skill/SKILL.md b/default/omarchy-skill/SKILL.md index b0ab7000..7bd905b1 100644 --- a/default/omarchy-skill/SKILL.md +++ b/default/omarchy-skill/SKILL.md @@ -129,7 +129,7 @@ Run `omarchy --help` for the full list. The most common groups: | `omarchy restart` | Restart a service/app | `omarchy restart shell` | | `omarchy toggle` | Toggle feature on/off | `omarchy toggle nightlight` | | `omarchy theme` | Theme management | `omarchy theme set ` | -| `omarchy bar` | Bar layout and widgets | `omarchy bar plugin move omarchy.clock --section right` | +| `omarchy bar` | Bar layout and widgets | `omarchy bar move omarchy.clock --section right` | | `omarchy plugin` | Manage/clone shell plugins | `omarchy plugin clone omarchy.clock` | | `omarchy hook` | Install automation hooks | `omarchy hook install theme-set