#!/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 --section center --before omarchy.weather | 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 --section center --before omarchy.weather 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 } # --------------------------------------------------------------------- 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:-right}" 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 )) && [[ $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