Simplify bar plugin management (#6435)

This commit is contained in:
David Heinemeier Hansson
2026-07-29 22:39:55 -04:00
committed by GitHub
parent 3304b79c92
commit 57ea0b4cd0
26 changed files with 886 additions and 835 deletions
+203 -17
View File
@@ -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 <id> | reset | defaults | position <top|bottom|left|right> | transparent <true|false|toggle>
# 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 <id> | reset | defaults | position <top|bottom|left|right> | transparent <true|false|toggle> | move <id> [placement] | set <id> <key> <value> [--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 <command> [args...]
defaults Restore the default bar and service widgets
position <top|bottom|left|right> Bar position
transparent <true|false|toggle> Bar transparency
move <id> [placement] Move a widget within or between sections
set <id> <key> <value> [--json] [placement]
Set a per-widget option
Bar widgets are added, moved, removed, and configured with 'omarchy bar plugin'.
Placement:
--section <left|center|right> Target section
--index <n> Target index
--before <id> Insert before a widget
--after <id> Insert after a widget
--from-section <section> Source section
--from-index <n> 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
-503
View File
@@ -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 <id> [placement] | move <id> [placement] | remove <id> [placement] | set <id> <key> <value> [--json] [placement] | replace <old-id> <new-id>
# 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 <<USAGE
Usage: omarchy bar plugin <command> [args...]
add <id> [placement] Add a bar widget
move <id> [placement] [--from-section S --from-index N]
Move a widget within/between sections
remove <id> [placement] (alias: rm, drop)
Remove a widget
set <id> <key> <value> [--json] [placement]
Set a per-widget option
replace <old-id> <new-id> Replace the first instance of a widget id
Placement (for add/move/remove/set):
--section <left|center|right> Target section
--index <n> Insert/remove at index in the target section
--before <id> Insert before the first matching widget
--after <id> Insert after the first matching widget
--from-section <section> Source section (move/remove/set)
--from-index <n> 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 <<JQ
$JQ_DEFS $NORMALIZE
| (.bar.layout.left + .bar.layout.center + .bar.layout.right | map(entry_id) | index(\$id)) as \$existing
| if \$existing != null and \$allowDuplicate != "true" then
error(\$id + " is already in the bar; use 'omarchy bar plugin move' or pass --duplicate")
else . end
| resolve_target(\$defaultSection) as \$target
| .bar.layout[\$target.section] = (.bar.layout[\$target.section][0:\$target.index] + [{id: \$id}] + .bar.layout[\$target.section][\$target.index:])
JQ
)
commit "$prog" \
--arg id "$id" \
--arg defaultSection "$default_section" \
--arg targetSection "$PLACEMENT_SECTION" \
--arg targetIndex "$PLACEMENT_INDEX" \
--arg before "$PLACEMENT_BEFORE" \
--arg after "$PLACEMENT_AFTER" \
--arg allowDuplicate "$PLACEMENT_DUPLICATE"
fi
echo "Added $id to the bar"
}
cmd_move() {
local id="${1:-}"
[[ -n $id ]] || fail "move 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"
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 <<JQ
$JQ_DEFS $NORMALIZE
| resolve_source as \$source
| .bar.layout[\$source.section] = (.bar.layout[\$source.section][0:\$source.index] + .bar.layout[\$source.section][\$source.index+1:])
| (\$defaultSection | if . == "" then \$source.section else . end) as \$fallback
| resolve_target(\$fallback) as \$target
| (\$source.entry | if type == "object" then .id = \$id else . end) as \$entry
| .bar.layout[\$target.section] = (.bar.layout[\$target.section][0:\$target.index] + [\$entry] + .bar.layout[\$target.section][\$target.index:])
JQ
)
commit "$prog" \
--arg id "$id" \
--arg defaultSection "$default_section" \
--arg targetSection "$target_section" \
--arg targetIndex "$PLACEMENT_INDEX" \
--arg before "$PLACEMENT_BEFORE" \
--arg after "$PLACEMENT_AFTER" \
--arg fromSection "$PLACEMENT_FROM_SECTION" \
--arg fromIndex "$PLACEMENT_FROM_INDEX" \
--arg widgetId "$id"
echo "Moved $id"
}
cmd_remove() {
local id=""
if (( $# > 0 )) && [[ $1 != --* ]]; then
id="$1"
shift
fi
parse_placement "$@"
local prog
prog=$(cat <<JQ
$JQ_DEFS $NORMALIZE
| if \$fromIndex != "" then
resolve_source as \$source
| .bar.layout[\$source.section] = (.bar.layout[\$source.section][0:\$source.index] + .bar.layout[\$source.section][\$source.index+1:])
elif \$targetIndex != "" then
if \$targetSection == "" then error("--index requires --section for remove") else . end
| (\$targetIndex | tonumber) as \$index
| .bar.layout[\$targetSection][\$index] as \$entry
| if \$widgetId != "" and (\$entry | entry_id) != \$widgetId then
error("widget at " + \$targetSection + "[" + (\$index | tostring) + "] is not " + \$widgetId)
else . end
| .bar.layout[\$targetSection] = (.bar.layout[\$targetSection][0:\$index] + .bar.layout[\$targetSection][\$index+1:])
else
find_all(\$widgetId; \$targetSection) as \$matches
| if (\$matches | length) == 0 then
error("could not find widget " + \$widgetId + (\$targetSection | if . == "" then "" else " in " + . end))
elif \$removeAll != "true" then
(\$matches | sort_by(.section) | reverse | .[0]) as \$first
| .bar.layout[\$first.section] = (.bar.layout[\$first.section][0:\$first.index] + .bar.layout[\$first.section][\$first.index+1:])
else
reduce (\$matches | sort_by(.section) | reverse | .[]) as \$m (.;
.bar.layout[\$m.section] = (.bar.layout[\$m.section][0:\$m.index] + .bar.layout[\$m.section][\$m.index+1:]))
end
end
JQ
)
commit "$prog" \
--arg widgetId "$id" \
--arg targetSection "$PLACEMENT_SECTION" \
--arg targetIndex "$PLACEMENT_INDEX" \
--arg fromSection "$PLACEMENT_FROM_SECTION" \
--arg fromIndex "$PLACEMENT_FROM_INDEX" \
--arg removeAll "$PLACEMENT_ALL"
if [[ -n $id ]]; then
echo "Removed $id from the bar"
else
echo "Removed bar widget"
fi
}
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"
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 <<JQ
$JQ_DEFS $NORMALIZE
| if \$fromIndex != "" then
resolve_source as \$source
| .bar.layout[\$source.section][\$source.index] = (.bar.layout[\$source.section][\$source.index] | if type == "object" then .[\$key] = \$value else error("widget entry must be an object") end)
elif \$targetIndex != "" then
if \$targetSection == "" then error("--index requires --section for set") else . end
| (\$targetIndex | tonumber) as \$index
| .bar.layout[\$targetSection][\$index] as \$entry
| if (\$entry | entry_id) != \$widgetId then
error("widget at " + \$targetSection + "[" + (\$index | tostring) + "] is not " + \$widgetId)
elif \$entry | type != "object" then
error("widget entry must be an object")
else . end
| .bar.layout[\$targetSection][\$index][\$key] = \$value
else
find_all(\$widgetId; \$targetSection) as \$matches
| if (\$matches | length) == 0 then
error("could not find widget " + \$widgetId + (\$targetSection | if . == "" then "" else " in " + . end))
else
(\$matches | sort_by(.section) | .[0]) as \$first
| if \$first.entry | type != "object" then error("widget entry must be an object") else . end
| .bar.layout[\$first.section][\$first.index][\$key] = \$value
end
end
JQ
)
commit "$prog" \
--arg widgetId "$id" \
--arg key "$key" \
"${value_arg[@]}" \
--arg targetSection "$PLACEMENT_SECTION" \
--arg targetIndex "$PLACEMENT_INDEX" \
--arg fromSection "$PLACEMENT_FROM_SECTION" \
--arg fromIndex "$PLACEMENT_FROM_INDEX"
echo "Set $key on $id"
}
cmd_replace() {
local old="${1:-}"
local new="${2:-}"
[[ -n $old ]] || fail "replace requires the source widget id"
[[ -n $new ]] || fail "replace requires the replacement widget id"
(( $# == 2 )) || fail "replace takes two widget ids"
local prog
prog=$(cat <<JQ
$JQ_DEFS $NORMALIZE
| resolve_source as \$source
| .bar.layout[\$source.section][\$source.index] = (
\$source.entry | if type == "object" then .id = \$new else { id: \$new } end
)
JQ
)
# resolve_source finds by widget id when no placement is given; replace
# takes no placement flags, so pass empty placement explicitly.
commit "$prog" \
--arg widgetId "$old" \
--arg new "$new" \
--arg fromSection "" \
--arg fromIndex ""
echo "Replaced $old with $new"
}
# --------------------------------------------------------------------- dispatch
command="${1:-}"
(( $# > 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
+1 -1
View File
@@ -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 &
+1 -1
View File
@@ -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
+12 -14
View File
@@ -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
+2 -4
View File
@@ -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
+3 -36
View File
@@ -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 <<USAGE
Usage: omarchy plugin clone <source-id>
@@ -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" \
+62 -5
View File
@@ -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")))
+2 -20
View File
@@ -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."
+1 -1
View File
@@ -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 ""
+1 -1
View File
@@ -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