Files
arthur-os/bin/omarchy-config-shell-bar

616 lines
17 KiB
Bash
Executable File

#!/bin/bash
# omarchy:summary=Mutate the user shell.json bar layout and active bar option
# omarchy:args=show | options [--json] | use <plugin> | reset | list [--json] [--all] | add [plugin] [left|center|right] | remove [plugin] | drop [plugin] | position <top|bottom|left|right> | transparent <true|false>
# omarchy:examples=omarchy config shell bar show | omarchy config shell bar options | omarchy config shell bar use local.neon-bar | omarchy config shell bar reset | omarchy config shell bar list | omarchy config shell bar add omarchy.tailscale | omarchy config shell bar position top | omarchy config shell bar transparent true
set -euo pipefail
CONFIG_FILE="$HOME/.config/omarchy/shell.json"
OMARCHY_ROOT="${OMARCHY_PATH:-}"
DEFAULTS_FILE="$OMARCHY_ROOT/config/omarchy/shell.json"
usage() {
echo "Usage: omarchy-config-shell-bar show | options [--json] | use <plugin> | reset | list [--json] [--all] | add [plugin] [left|center|right] | remove [plugin] | drop [plugin] | position <top|bottom|left|right> | transparent <true|false>" >&2
}
fail() {
echo "omarchy-config-shell-bar: $*" >&2
exit 1
}
refresh_shell_config() {
if ! omarchy-shell shell reloadConfig >/dev/null 2>&1; then
omarchy-shell -q shell rescanPlugins >/dev/null 2>&1 || true
fi
}
source_file() {
local source="$CONFIG_FILE"
if [[ ! -s $source ]]; then
[[ -n $OMARCHY_ROOT ]] || fail "OMARCHY_PATH is not set"
source="$DEFAULTS_FILE"
fi
[[ -s $source ]] || fail "could not find shell config or defaults"
printf '%s\n' "$source"
}
bar_widget_manifest_paths() {
{
if [[ -n $OMARCHY_ROOT && -d $OMARCHY_ROOT/shell/plugins ]]; then
find "$OMARCHY_ROOT/shell/plugins" -mindepth 2 -maxdepth 4 -type f \( -name manifest.json -o -name '*.manifest.json' \) 2>/dev/null
fi
if [[ -d $HOME/.config/omarchy/plugins ]]; then
find -L "$HOME/.config/omarchy/plugins" -mindepth 2 -maxdepth 2 -type f -name manifest.json 2>/dev/null
fi
} | sort -u
}
bar_option_manifest_paths() {
{
if [[ -n $OMARCHY_ROOT && -d $OMARCHY_ROOT/shell/plugins ]]; then
find "$OMARCHY_ROOT/shell/plugins" -mindepth 2 -maxdepth 4 -type f \( -name manifest.json -o -name '*.manifest.json' \) 2>/dev/null
fi
if [[ -d $HOME/.config/omarchy/plugins ]]; then
find -L "$HOME/.config/omarchy/plugins" -mindepth 2 -maxdepth 2 -type f -name manifest.json 2>/dev/null
fi
} | sort -u
}
current_bar_option_id() {
jq -r '(.bar.id // "omarchy.bar") | tostring' "$(source_file)"
}
available_bar_options_json() {
local current_id
local manifest_paths=()
current_id=$(current_bar_option_id)
mapfile -t manifest_paths < <(bar_option_manifest_paths)
if (( ${#manifest_paths[@]} == 0 )); then
printf '[]\n'
return
fi
jq -s --arg currentId "$current_id" '
map(
select(((.kinds // []) | index("bar")) and ((.entryPoints.bar // "") != "")) |
{
id: (.id // ""),
name: (.name // .id // ""),
description: (.description // ""),
firstParty: ((.id // "") | startswith("omarchy.")),
active: ((.id // "") == $currentId)
} |
select(.id != "")
) |
unique_by(.id) |
sort_by((if .id == "omarchy.bar" then 0 else 1 end), .name, .id)
' "${manifest_paths[@]}"
}
print_bar_options() {
local json="false"
while (( $# > 0 )); do
case "$1" in
--json)
json="true"
;;
-h|--help)
usage
exit 0
;;
*)
fail "unknown options option: $1"
;;
esac
shift
done
if [[ $json == "true" ]]; then
available_bar_options_json
return
fi
available_bar_options_json | jq -r '
.[] |
[
.id,
(if .active then "active" else "available" end),
(if .firstParty then "built-in" else "plugin" end),
.name,
.description
] | @tsv
' | awk -F '\t' '
BEGIN { printf "%-32s %-10s %-8s %-22s %s\n", "ID", "STATUS", "SOURCE", "NAME", "DESCRIPTION" }
{ printf "%-32s %-10s %-8s %-22s %s\n", $1, $2, $3, $4, $5 }
'
}
bar_option_exists() {
local id="$1"
available_bar_options_json | jq -e --arg id "$id" 'any(.[]; .id == $id)' >/dev/null
}
current_bar_ids_json() {
jq -c '
def array_or_empty: if type == "array" then . else [] end;
def entry_id: if type == "object" then (.id // "" | tostring) else tostring end;
["left", "center", "right"] as $sections |
[ $sections[] as $section |
((.bar.layout[$section] // []) | array_or_empty)[] |
entry_id |
select(length > 0)
]
' "$(source_file)"
}
current_bar_entries_json() {
jq -c '
def array_or_empty: if type == "array" then . else [] end;
def entry_id($entry): if ($entry | type) == "object" then ($entry.id // "" | tostring) else ($entry | tostring) end;
["left", "center", "right"] as $sections |
[ $sections[] as $section |
((.bar.layout[$section] // []) | array_or_empty | to_entries[]) |
{ section: $section, index: .key, id: entry_id(.value) } |
select(.id | length > 0)
]
' "$(source_file)"
}
available_widgets_json() {
local include_all="$1"
local current_ids
local manifest_paths=()
current_ids=$(current_bar_ids_json)
mapfile -t manifest_paths < <(bar_widget_manifest_paths)
if (( ${#manifest_paths[@]} == 0 )); then
printf '[]\n'
return
fi
jq -s --argjson currentIds "$current_ids" --argjson includeAll "$include_all" '
map(
select(((.kinds // []) | index("bar-widget")) and ((.entryPoints.barWidget // "") != "")) |
{
id: (.id // ""),
name: (.barWidget.displayName // .name // .id // ""),
description: (.barWidget.description // .description // ""),
category: (.barWidget.category // "Plugin"),
allowMultiple: (.barWidget.allowMultiple == true)
} |
select(.id != "")
) |
unique_by(.id) |
map(. as $widget |
$widget + {
inBar: (($currentIds | index($widget.id)) != null),
addable: ($widget.allowMultiple or (($currentIds | index($widget.id)) == null))
}
) |
map(select($includeAll or .addable)) |
sort_by(.category, .name, .id)
' "${manifest_paths[@]}"
}
print_available_widgets() {
local include_all="false"
local json="false"
while (( $# > 0 )); do
case "$1" in
--all)
include_all="true"
;;
--json)
json="true"
;;
-h|--help)
usage
exit 0
;;
*)
fail "unknown list option: $1"
;;
esac
shift
done
if [[ $json == "true" ]]; then
available_widgets_json "$include_all"
return
fi
available_widgets_json "$include_all" | jq -r '
.[] |
[
.id,
(if .inBar and .allowMultiple then "addable*" elif .inBar then "in-bar" else "addable" end),
.category,
.name,
.description
] | @tsv
' | awk -F '\t' '
BEGIN { printf "%-28s %-10s %-14s %-22s %s\n", "ID", "STATUS", "CATEGORY", "NAME", "DESCRIPTION" }
{ printf "%-28s %-10s %-14s %-22s %s\n", $1, $2, $3, $4, $5 }
'
}
choose_widget() {
local widgets
local options=()
local selected=""
widgets=$(available_widgets_json false)
if ! jq -e 'length > 0' <<<"$widgets" >/dev/null; then
fail "no addable bar widgets found"
fi
mapfile -t options < <(jq -r '
.[] |
[.id, .name, .category, .description] | @tsv
' <<<"$widgets" | awk -F '\t' '{ printf "%-28s %-22s [%s] %s\n", $1, $2, $3, $4 }')
if [[ -t 0 || -t 2 ]]; then
if omarchy-cmd-present gum; then
selected=$(printf '%s\n' "${options[@]}" | gum filter --header "Add bar widget" --placeholder "Search addable widgets..." --limit 1 --height 14) || return 1
elif omarchy-cmd-present fzf; then
selected=$(printf '%s\n' "${options[@]}" | fzf --prompt "Add bar widget > " --height 40% --layout reverse --border) || return 1
else
print_available_widgets >&2
fail "gum or fzf is required for interactive widget selection"
fi
else
print_available_widgets >&2
fail "widget id is required; use list to see addable widgets"
fi
selected="${selected%%[[:space:]]*}"
[[ -n $selected ]] || return 1
printf '%s\n' "$selected"
}
choose_removal() {
local entries
local widgets
local options=()
local selected=""
local location=""
local section=""
local index=""
entries=$(current_bar_entries_json)
widgets=$(available_widgets_json true)
if ! jq -e 'length > 0' <<<"$entries" >/dev/null; then
fail "no bar widgets are currently configured"
fi
mapfile -t options < <(jq -r --argjson widgets "$widgets" '
def widget_meta($id): first($widgets[]? | select(.id == $id)) // {};
.[] as $entry |
(widget_meta($entry.id)) as $meta |
[
"\($entry.section)[\($entry.index)]",
$entry.id,
($meta.name // $entry.id),
($meta.category // "")
] | @tsv
' <<<"$entries" | awk -F '\t' '{ printf "%-10s %-28s %-22s %s\n", $1, $2, $3, $4 }')
if [[ -t 0 || -t 2 ]]; then
if omarchy-cmd-present gum; then
selected=$(printf '%s\n' "${options[@]}" | gum filter --header "Remove bar widget" --placeholder "Search current widgets..." --limit 1 --height 14) || return 1
elif omarchy-cmd-present fzf; then
selected=$(printf '%s\n' "${options[@]}" | fzf --prompt "Remove bar widget > " --height 40% --layout reverse --border) || return 1
else
jq -r '.[] | "\(.section)[\(.index)]\t\(.id)"' <<<"$entries" >&2
fail "gum or fzf is required for interactive widget selection"
fi
else
jq -r '.[] | "\(.section)[\(.index)]\t\(.id)"' <<<"$entries" >&2
fail "widget id is required; use show to see current widgets"
fi
location="${selected%%[[:space:]]*}"
section="${location%%[*}"
index="${location#*[}"
index="${index%]}"
[[ $section =~ ^(left|center|right)$ ]] || return 1
[[ $index =~ ^[0-9]+$ ]] || return 1
printf '%s\t%s\n' "$section" "$index"
}
command="${1:-}"
case "$command" in
show|current)
if (( $# != 1 )); then
usage
exit 1
fi
jq '.bar // {}' "$(source_file)"
;;
selected|active)
if (( $# != 1 )); then
usage
exit 1
fi
current_bar_option_id
;;
options|option)
shift
print_bar_options "$@"
;;
use)
if (( $# != 2 )); then
usage
exit 1
fi
plugin="${2:-}"
[[ -n $plugin ]] || fail "bar option id is required"
if [[ $plugin == "default" || $plugin == "built-in" ]]; then
plugin="omarchy.bar"
fi
bar_option_exists "$plugin" || fail "$plugin is not a known bar option; run 'omarchy config shell bar options'"
mkdir -p "$(dirname "$CONFIG_FILE")"
source="$(source_file)"
tmp=$(mktemp)
trap 'rm -f "$tmp"' EXIT
if [[ $plugin == "omarchy.bar" ]]; then
jq '
def object_or_empty: if type == "object" then . else {} end;
object_or_empty
| .version = 1
| .bar = (.bar | object_or_empty)
| del(.bar.id)
' "$source" >"$tmp"
else
jq --arg plugin "$plugin" '
def object_or_empty: if type == "object" then . else {} end;
object_or_empty
| .version = 1
| .bar = (.bar | object_or_empty)
| .bar.id = $plugin
' "$source" >"$tmp"
fi
mv "$tmp" "$CONFIG_FILE"
trap - EXIT
omarchy-shell -q shell rescanPlugins >/dev/null 2>&1 || true
refresh_shell_config
;;
reset)
if (( $# != 1 )); then
usage
exit 1
fi
mkdir -p "$(dirname "$CONFIG_FILE")"
source="$(source_file)"
tmp=$(mktemp)
trap 'rm -f "$tmp"' EXIT
jq '
def object_or_empty: if type == "object" then . else {} end;
object_or_empty
| .version = 1
| .bar = (.bar | object_or_empty)
| del(.bar.id)
' "$source" >"$tmp"
mv "$tmp" "$CONFIG_FILE"
trap - EXIT
refresh_shell_config
;;
list|available|widgets)
shift
print_available_widgets "$@"
;;
add)
if (( $# > 3 )); then
usage
exit 1
fi
if (( $# == 1 )); then
plugin=$(choose_widget) || exit 1
else
plugin="${2:-}"
fi
section="${3:-right}"
[[ -n $plugin ]] || fail "plugin is required"
[[ $section =~ ^(left|center|right)$ ]] || fail "section must be left, center, or right"
mkdir -p "$(dirname "$CONFIG_FILE")"
source="$(source_file)"
tmp=$(mktemp)
trap 'rm -f "$tmp"' EXIT
jq --arg section "$section" --arg plugin "$plugin" '
def object_or_empty: if type == "object" then . else {} end;
def array_or_empty: if type == "array" then . else [] end;
def entry_id: if type == "object" then (.id // "" | tostring) else tostring end;
def append_anchor($section):
{
left: "omarchy.workspaces",
center: "omarchy.weather",
right: "omarchy.tray"
}[$section];
def insert_after_anchor($entries; $entry; $anchor):
($entries | map(entry_id) | index($anchor)) as $index
| if $index == null then
$entries + [$entry]
else
$entries[0:$index + 1] + [$entry] + $entries[$index + 1:]
end;
object_or_empty
| .version = 1
| .bar = (.bar | object_or_empty)
| .bar.layout = (.bar.layout | object_or_empty)
| .bar.layout.left = (.bar.layout.left | array_or_empty | map(select(entry_id != $plugin)))
| .bar.layout.center = (.bar.layout.center | array_or_empty | map(select(entry_id != $plugin)))
| .bar.layout.right = (.bar.layout.right | array_or_empty | map(select(entry_id != $plugin)))
| .plugins = (.plugins | array_or_empty)
| .bar.layout[$section] = insert_after_anchor(.bar.layout[$section]; { id: $plugin }; append_anchor($section))
' "$source" >"$tmp"
mv "$tmp" "$CONFIG_FILE"
trap - EXIT
refresh_shell_config
;;
position)
if (( $# != 2 )); then
usage
exit 1
fi
position="${2:-}"
[[ $position =~ ^(top|bottom|left|right)$ ]] || fail "position must be top, bottom, left, or right"
mkdir -p "$(dirname "$CONFIG_FILE")"
source="$(source_file)"
tmp=$(mktemp)
trap 'rm -f "$tmp"' EXIT
jq --arg position "$position" '
def object_or_empty: if type == "object" then . else {} end;
object_or_empty
| .version = 1
| .bar = (.bar | object_or_empty)
| .bar.position = $position
' "$source" >"$tmp"
mv "$tmp" "$CONFIG_FILE"
trap - EXIT
refresh_shell_config
;;
transparent)
if (( $# != 2 )); then
usage
exit 1
fi
transparent="${2:-}"
[[ $transparent =~ ^(true|false)$ ]] || fail "transparent must be true or false"
mkdir -p "$(dirname "$CONFIG_FILE")"
source="$(source_file)"
tmp=$(mktemp)
trap 'rm -f "$tmp"' EXIT
jq --argjson transparent "$transparent" '
def object_or_empty: if type == "object" then . else {} end;
object_or_empty
| .version = 1
| .bar = (.bar | object_or_empty)
| .bar.transparent = $transparent
' "$source" >"$tmp"
mv "$tmp" "$CONFIG_FILE"
trap - EXIT
refresh_shell_config
;;
drop|remove|rm)
if (( $# > 2 )); then
usage
exit 1
fi
plugin=""
target_section=""
target_index=""
if (( $# == 1 )); then
removal=$(choose_removal) || exit 1
target_section="${removal%%$'\t'*}"
target_index="${removal#*$'\t'}"
else
plugin="${2:-}"
[[ -n $plugin ]] || fail "plugin is required"
fi
mkdir -p "$(dirname "$CONFIG_FILE")"
source="$(source_file)"
tmp=$(mktemp)
trap 'rm -f "$tmp"' EXIT
if [[ -n $target_section ]]; then
jq --arg section "$target_section" --argjson index "$target_index" '
def object_or_empty: if type == "object" then . else {} end;
def array_or_empty: if type == "array" then . else [] end;
object_or_empty
| .version = 1
| .bar = (.bar | object_or_empty)
| .bar.layout = (.bar.layout | object_or_empty)
| .bar.layout.left = (.bar.layout.left | array_or_empty)
| .bar.layout.center = (.bar.layout.center | array_or_empty)
| .bar.layout.right = (.bar.layout.right | array_or_empty)
| if (.bar.layout[$section] | length) <= $index then
error("no widget at " + $section + "[" + ($index | tostring) + "]")
else
.bar.layout[$section] = (.bar.layout[$section][0:$index] + .bar.layout[$section][($index + 1):])
end
| .plugins = (.plugins | array_or_empty)
' "$source" >"$tmp"
else
jq --arg plugin "$plugin" '
def object_or_empty: if type == "object" then . else {} end;
def array_or_empty: if type == "array" then . else [] end;
def entry_id: if type == "object" then (.id // "" | tostring) else tostring end;
object_or_empty
| .version = 1
| .bar = (.bar | object_or_empty)
| .bar.layout = (.bar.layout | object_or_empty)
| .bar.layout.left = (.bar.layout.left | array_or_empty | map(select(entry_id != $plugin)))
| .bar.layout.center = (.bar.layout.center | array_or_empty | map(select(entry_id != $plugin)))
| .bar.layout.right = (.bar.layout.right | array_or_empty | map(select(entry_id != $plugin)))
| .plugins = (.plugins | array_or_empty)
' "$source" >"$tmp"
fi
mv "$tmp" "$CONFIG_FILE"
trap - EXIT
refresh_shell_config
;;
*)
usage
exit 1
;;
esac