mirror of
https://github.com/arthur-pbty/arthur-os.git
synced 2026-08-02 04:37:49 +02:00
1295 lines
38 KiB
Bash
Executable File
1295 lines
38 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# omarchy:summary=Manage Omarchy shell plugins and bar widgets
|
|
# omarchy:group=plugin
|
|
# omarchy:args=<list|rescan|enable|disable|clone|edit|bar> [...]
|
|
# omarchy:examples=omarchy plugin list | omarchy plugin clone omarchy.clock local.clock --with ai | omarchy plugin edit local.clock --with editor | omarchy plugin enable acme.weather --section right | omarchy plugin bar settings
|
|
|
|
set -euo pipefail
|
|
|
|
CONFIG_FILE="$HOME/.config/omarchy/shell.json"
|
|
|
|
usage() {
|
|
cat <<USAGE
|
|
Usage: omarchy-plugin <command> [args...]
|
|
|
|
Plugin commands:
|
|
list [--json] List discovered shell plugins
|
|
rescan Rescan ~/.config/omarchy/plugins
|
|
enable <id> [placement] Enable a plugin
|
|
disable <id> Disable a plugin
|
|
clone [source] [new-id] [options] Clone a built-in or user plugin
|
|
edit [id] [--with <ai|editor>] [--prompt <prompt>]
|
|
Open a user plugin for editing
|
|
|
|
Add from sources (see 'omarchy plugin <command> --help'):
|
|
source <add|list|remove|refresh> Manage trusted plugin source repos
|
|
available List plugins offered by your sources
|
|
add [id] [--from <src>] [--enable] [--review] [--yes]
|
|
Add a plugin from a trusted source
|
|
update [id] [--all] [--review] [--yes]
|
|
Update added plugins (shows a diff)
|
|
remove [id] [--yes] Remove an installed plugin
|
|
validate <plugin-folder> Check a plugin's manifest (for authors)
|
|
|
|
Source/add commands run their own binaries; the rest are handled here.
|
|
Plugins are unsandboxed code — review what you add and enable.
|
|
|
|
Clone options:
|
|
--name <name> Display name for the clone
|
|
--with <ai|editor> Open the clone with AI or editor
|
|
--prompt <prompt> Initial prompt when opening with AI
|
|
--replace Replace the first source bar instance
|
|
--add [placement] Add cloned bar widget to the bar
|
|
--use Use cloned bar options as the active bar
|
|
|
|
Bar widget commands:
|
|
bar settings Open the inline bar config panel
|
|
bar options [--json] List available bar options
|
|
bar use <id> Use a bar option as the active bar
|
|
bar reset Return to the built-in Omarchy bar
|
|
bar list [--json] List current bar layout
|
|
bar add <id> [placement] [--duplicate]
|
|
bar move <id> [placement] [--from-section SECTION] [--from-index N]
|
|
bar remove <id> [--section SECTION] [--all]
|
|
bar remove --section SECTION --index N
|
|
bar set <id> <key> <value> [--json] [--section SECTION] [--index N]
|
|
|
|
Placement options:
|
|
--section <left|center|right> Target bar section
|
|
--index <n> Insert at index in the target section
|
|
--before <id> Insert before the first matching widget
|
|
--after <id> Insert after the first matching widget
|
|
|
|
Examples:
|
|
omarchy plugin list
|
|
omarchy plugin rescan
|
|
omarchy plugin clone
|
|
omarchy plugin clone omarchy.clock local.clock --name "My Clock" --replace --with ai --prompt "Customize this clock"
|
|
omarchy plugin edit local.clock --with editor
|
|
omarchy plugin edit local.clock --with ai --prompt "Customize this clock"
|
|
omarchy plugin enable acme.weather --section right
|
|
omarchy plugin bar options
|
|
omarchy plugin bar use local.neon-bar
|
|
omarchy plugin bar reset
|
|
omarchy plugin bar add acme.weather --section right --before omarchy.tray
|
|
omarchy plugin bar move omarchy.clock --section center --index 0
|
|
omarchy plugin bar remove acme.weather
|
|
omarchy plugin bar set omarchy.clock format HH:mm
|
|
omarchy plugin bar set omarchy.indicators items '["Dnd","NightLight"]' --json
|
|
USAGE
|
|
}
|
|
|
|
fail() {
|
|
echo "omarchy-plugin: $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
require_omarchy_path() {
|
|
[[ -n ${OMARCHY_PATH:-} ]] || fail "OMARCHY_PATH is not set"
|
|
}
|
|
|
|
require_command() {
|
|
local command_name="$1"
|
|
command -v "$command_name" >/dev/null 2>&1 || fail "$command_name is required"
|
|
}
|
|
|
|
plugin_discovered() {
|
|
local id="$1" plugins
|
|
plugins=$(omarchy-shell shell listPlugins 2>/dev/null) || return 1
|
|
jq -e --arg id "$id" 'any(.[]; .id == $id)' <<<"$plugins" >/dev/null 2>&1
|
|
}
|
|
|
|
wait_for_plugin_discovery() {
|
|
local id="$1" attempt
|
|
for (( attempt = 0; attempt < 40; attempt++ )); do
|
|
plugin_discovered "$id" && return 0
|
|
sleep 0.05
|
|
done
|
|
return 1
|
|
}
|
|
|
|
section_valid() {
|
|
[[ $1 =~ ^(left|center|right)$ ]]
|
|
}
|
|
|
|
validate_section() {
|
|
local section="$1"
|
|
section_valid "$section" || fail "section must be left, center, or right"
|
|
}
|
|
|
|
validate_index() {
|
|
local value="$1"
|
|
[[ $value =~ ^[0-9]+$ ]] || fail "index must be a non-negative integer"
|
|
}
|
|
|
|
canonical_widget_id() {
|
|
printf '%s' "$1"
|
|
}
|
|
|
|
validate_plugin_id() {
|
|
local id="$1"
|
|
[[ $id =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]] || fail "plugin id must contain only letters, numbers, dots, underscores, and dashes"
|
|
[[ $id == *.* ]] || fail "plugin id must be namespaced, e.g. local.clock"
|
|
[[ $id != omarchy.* ]] || fail "plugin ids beginning with omarchy. are reserved for built-ins"
|
|
[[ $(canonical_widget_id "$id") == "$id" ]] || fail "$id is a reserved built-in widget id"
|
|
}
|
|
|
|
print_plugins() {
|
|
local json="false"
|
|
|
|
while (( $# > 0 )); do
|
|
case "$1" in
|
|
--json)
|
|
json="true"
|
|
;;
|
|
-h | --help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
fail "unknown list option: $1"
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
local plugins
|
|
plugins=$(omarchy-shell shell listPlugins) || fail "could not list plugins; is omarchy-shell running?"
|
|
|
|
if [[ $json == "true" ]]; then
|
|
printf '%s\n' "$plugins"
|
|
return
|
|
fi
|
|
|
|
require_command jq
|
|
jq -r '
|
|
sort_by(.id)[] |
|
|
[ .id,
|
|
(if .enabled then "enabled" else "disabled" end),
|
|
(if .firstParty then "first-party" else "third-party" end),
|
|
((.kinds // []) | join(",")),
|
|
(.name // "")
|
|
] | @tsv
|
|
' <<<"$plugins" | awk -F '\t' '
|
|
BEGIN { printf "%-32s %-9s %-11s %-18s %s\n", "ID", "STATE", "SOURCE", "KINDS", "NAME" }
|
|
{ printf "%-32s %-9s %-11s %-18s %s\n", $1, $2, $3, $4, $5 }
|
|
'
|
|
}
|
|
|
|
rescan_plugins() {
|
|
(( $# == 0 )) || fail "rescan does not take arguments"
|
|
omarchy-shell shell rescanPlugins || fail "could not rescan plugins; is omarchy-shell running?"
|
|
echo "Plugins rescanned"
|
|
}
|
|
|
|
plugin_enabled() {
|
|
local enabled="$1"
|
|
local id="${2:-}"
|
|
[[ -n $id ]] || fail "plugin id is required"
|
|
shift 2
|
|
|
|
if [[ $enabled != "true" && $# -gt 0 ]]; then
|
|
fail "disable does not take placement options"
|
|
fi
|
|
|
|
id=$(canonical_widget_id "$id")
|
|
|
|
omarchy-shell shell rescanPlugins >/dev/null 2>&1 || true
|
|
if [[ $enabled == "true" ]]; then
|
|
require_command jq
|
|
wait_for_plugin_discovery "$id" || fail "plugin '$id' was not discovered; is omarchy-shell running?"
|
|
fi
|
|
|
|
local result
|
|
result=$(omarchy-shell shell setPluginEnabled "$id" "$enabled") || fail "could not update plugin; is omarchy-shell running?"
|
|
[[ $result == "ok" ]] || fail "plugin '$id' is not known; run: omarchy plugin rescan"
|
|
|
|
if [[ $enabled == "true" && $# -gt 0 ]]; then
|
|
mutate_bar move --id "$id" "$@"
|
|
echo "Enabled and moved $id"
|
|
else
|
|
if [[ $enabled == "true" ]]; then
|
|
echo "Enabled $id"
|
|
else
|
|
echo "Disabled $id"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
config_source() {
|
|
require_omarchy_path
|
|
local defaults_file="$OMARCHY_PATH/config/omarchy/shell.json"
|
|
|
|
if [[ -s $CONFIG_FILE ]] && jq -e 'type == "object" and .version == 1' "$CONFIG_FILE" >/dev/null 2>&1; then
|
|
printf '%s' "$CONFIG_FILE"
|
|
elif [[ -s $defaults_file ]] && jq -e 'type == "object" and .version == 1' "$defaults_file" >/dev/null 2>&1; then
|
|
printf '%s' "$defaults_file"
|
|
else
|
|
fail "could not find a valid shell config or defaults file"
|
|
fi
|
|
}
|
|
|
|
bar_list() {
|
|
local json="false"
|
|
|
|
while (( $# > 0 )); do
|
|
case "$1" in
|
|
--json)
|
|
json="true"
|
|
;;
|
|
-h | --help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
fail "unknown bar list option: $1"
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
require_command jq
|
|
local source
|
|
source=$(config_source)
|
|
|
|
if [[ $json == "true" ]]; then
|
|
jq '.bar.layout // { left: [], center: [], right: [] }' "$source"
|
|
return
|
|
fi
|
|
|
|
jq -r '
|
|
(.bar.layout // { left: [], center: [], right: [] }) as $layout |
|
|
["left", "center", "right"][] as $section |
|
|
($layout[$section] // []) as $entries |
|
|
if ($entries | length) == 0 then
|
|
"\($section)\t-\t-"
|
|
else
|
|
$entries | to_entries[] | "\($section)\t\(.key)\t\(.value.id // "<missing>")"
|
|
end
|
|
' "$source" | awk '
|
|
BEGIN { printf "%-8s %-5s %s\n", "SECTION", "INDEX", "WIDGET" }
|
|
{ printf "%-8s %-5s %s\n", $1, $2, $3 }
|
|
'
|
|
}
|
|
|
|
mutate_bar() {
|
|
require_command jq
|
|
require_command python3
|
|
require_omarchy_path
|
|
|
|
local operation="$1"
|
|
shift
|
|
local id=""
|
|
local section=""
|
|
local index=""
|
|
local before=""
|
|
local after=""
|
|
local from_section=""
|
|
local from_index=""
|
|
local duplicate="false"
|
|
local all="false"
|
|
local key=""
|
|
local value=""
|
|
local value_is_json="false"
|
|
local new_id=""
|
|
|
|
while (( $# > 0 )); do
|
|
case "$1" in
|
|
--id)
|
|
id="${2-}"
|
|
shift 2
|
|
;;
|
|
--section)
|
|
section="${2:-}"
|
|
validate_section "$section"
|
|
shift 2
|
|
;;
|
|
--index)
|
|
index="${2:-}"
|
|
validate_index "$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
|
|
;;
|
|
--from-section)
|
|
from_section="${2:-}"
|
|
validate_section "$from_section"
|
|
shift 2
|
|
;;
|
|
--from-index)
|
|
from_index="${2:-}"
|
|
validate_index "$from_index"
|
|
shift 2
|
|
;;
|
|
--duplicate)
|
|
duplicate="true"
|
|
shift
|
|
;;
|
|
--all)
|
|
all="true"
|
|
shift
|
|
;;
|
|
--key)
|
|
key="${2:-}"
|
|
[[ -n $key ]] || fail "--key requires a value"
|
|
shift 2
|
|
;;
|
|
--new-id)
|
|
new_id="${2:-}"
|
|
[[ -n $new_id ]] || fail "--new-id requires a value"
|
|
shift 2
|
|
;;
|
|
--value)
|
|
value="${2:-}"
|
|
shift 2
|
|
;;
|
|
--json)
|
|
value_is_json="true"
|
|
shift
|
|
;;
|
|
*)
|
|
fail "unknown option: $1"
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ -n $before && -n $after ]]; then
|
|
fail "use only one of --before or --after"
|
|
fi
|
|
|
|
local source tmp
|
|
source=$(config_source)
|
|
mkdir -p "$(dirname "$CONFIG_FILE")"
|
|
tmp=$(mktemp)
|
|
|
|
OPERATION="$operation" \
|
|
WIDGET_ID="$id" \
|
|
TARGET_SECTION="$section" \
|
|
TARGET_INDEX="$index" \
|
|
TARGET_BEFORE="$before" \
|
|
TARGET_AFTER="$after" \
|
|
FROM_SECTION="$from_section" \
|
|
FROM_INDEX="$from_index" \
|
|
ALLOW_DUPLICATE="$duplicate" \
|
|
REMOVE_ALL="$all" \
|
|
SET_KEY="$key" \
|
|
SET_VALUE="$value" \
|
|
SET_VALUE_IS_JSON="$value_is_json" \
|
|
NEW_WIDGET_ID="$new_id" \
|
|
python3 - "$source" >"$tmp" <<'PY'
|
|
import json
|
|
import os
|
|
import sys
|
|
from copy import deepcopy
|
|
|
|
source = sys.argv[1]
|
|
operation = os.environ["OPERATION"]
|
|
def canonical(value):
|
|
return str(value or "")
|
|
|
|
|
|
widget_id = canonical(os.environ.get("WIDGET_ID", ""))
|
|
target_section = os.environ.get("TARGET_SECTION", "")
|
|
target_index = os.environ.get("TARGET_INDEX", "")
|
|
target_before = canonical(os.environ.get("TARGET_BEFORE", ""))
|
|
target_after = canonical(os.environ.get("TARGET_AFTER", ""))
|
|
from_section = os.environ.get("FROM_SECTION", "")
|
|
from_index = os.environ.get("FROM_INDEX", "")
|
|
allow_duplicate = os.environ.get("ALLOW_DUPLICATE") == "true"
|
|
remove_all = os.environ.get("REMOVE_ALL") == "true"
|
|
set_key = os.environ.get("SET_KEY", "")
|
|
set_value = os.environ.get("SET_VALUE", "")
|
|
set_value_is_json = os.environ.get("SET_VALUE_IS_JSON") == "true"
|
|
new_widget_id = os.environ.get("NEW_WIDGET_ID", "")
|
|
sections = ["left", "center", "right"]
|
|
|
|
|
|
def die(message):
|
|
print(f"omarchy-plugin: {message}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
def load_config(path):
|
|
with open(path, encoding="utf-8") as handle:
|
|
data = json.load(handle)
|
|
if not isinstance(data, dict):
|
|
die("shell config must be a JSON object")
|
|
data = deepcopy(data)
|
|
data["version"] = 1
|
|
data.setdefault("bar", {})
|
|
if not isinstance(data["bar"], dict):
|
|
data["bar"] = {}
|
|
data["bar"].setdefault("layout", {})
|
|
if not isinstance(data["bar"]["layout"], dict):
|
|
data["bar"]["layout"] = {}
|
|
for section in sections:
|
|
entries = data["bar"]["layout"].get(section)
|
|
if not isinstance(entries, list):
|
|
data["bar"]["layout"][section] = []
|
|
data.setdefault("plugins", [])
|
|
if not isinstance(data["plugins"], list):
|
|
data["plugins"] = []
|
|
return data
|
|
|
|
|
|
config = load_config(source)
|
|
layout = config["bar"]["layout"]
|
|
|
|
|
|
def find_entries(id_value=None, only_section=""):
|
|
found = []
|
|
for section in sections:
|
|
if only_section and section != only_section:
|
|
continue
|
|
for index, entry in enumerate(layout[section]):
|
|
if id_value is None or (isinstance(entry, dict) and canonical(entry.get("id")) == id_value):
|
|
found.append((section, index, entry))
|
|
return found
|
|
|
|
|
|
def entry_at(section, index):
|
|
if section not in sections:
|
|
die("section must be left, center, or right")
|
|
if index < 0 or index >= len(layout[section]):
|
|
die(f"no widget at {section}[{index}]")
|
|
return layout[section][index]
|
|
|
|
|
|
def parse_index(value):
|
|
if value == "":
|
|
return None
|
|
try:
|
|
parsed = int(value)
|
|
except ValueError:
|
|
die("index must be a non-negative integer")
|
|
if parsed < 0:
|
|
die("index must be a non-negative integer")
|
|
return parsed
|
|
|
|
|
|
def resolve_target(default_section="right"):
|
|
if target_before or target_after:
|
|
target_id = target_before or target_after
|
|
matches = find_entries(target_id, target_section)
|
|
if not matches:
|
|
scope = f" in {target_section}" if target_section else ""
|
|
die(f"could not find target widget {target_id}{scope}")
|
|
section, index, _entry = matches[0]
|
|
if target_after:
|
|
index += 1
|
|
return section, index
|
|
|
|
section = target_section or default_section
|
|
if section not in sections:
|
|
die("section must be left, center, or right")
|
|
index = parse_index(target_index)
|
|
if index is None:
|
|
index = len(layout[section])
|
|
if index > len(layout[section]):
|
|
index = len(layout[section])
|
|
return section, index
|
|
|
|
|
|
def source_location():
|
|
if from_index != "":
|
|
if not from_section:
|
|
die("--from-index requires --from-section")
|
|
index = parse_index(from_index)
|
|
entry = entry_at(from_section, index)
|
|
if widget_id and (not isinstance(entry, dict) or canonical(entry.get("id")) != widget_id):
|
|
die(f"widget at {from_section}[{index}] is not {widget_id}")
|
|
return from_section, index, entry
|
|
|
|
if not widget_id:
|
|
die("widget id is required")
|
|
matches = find_entries(widget_id, from_section)
|
|
if not matches:
|
|
scope = f" in {from_section}" if from_section else ""
|
|
die(f"could not find widget {widget_id}{scope}")
|
|
return matches[0]
|
|
|
|
|
|
if operation == "add":
|
|
if not widget_id:
|
|
die("widget id is required")
|
|
if not allow_duplicate and find_entries(widget_id):
|
|
die(f"{widget_id} is already in the bar; use 'bar move' or pass --duplicate")
|
|
section, index = resolve_target("right")
|
|
layout[section].insert(index, {"id": widget_id})
|
|
|
|
elif operation == "move":
|
|
old_section, old_index, entry = source_location()
|
|
entry = deepcopy(entry)
|
|
if isinstance(entry, dict):
|
|
entry["id"] = widget_id
|
|
del layout[old_section][old_index]
|
|
default_section = target_section or old_section
|
|
section, index = resolve_target(default_section)
|
|
layout[section].insert(index, entry)
|
|
|
|
elif operation == "remove":
|
|
if from_index != "":
|
|
old_section, old_index, _entry = source_location()
|
|
del layout[old_section][old_index]
|
|
elif target_index != "":
|
|
if not target_section:
|
|
die("--index requires --section for bar remove")
|
|
index = parse_index(target_index)
|
|
entry = entry_at(target_section, index)
|
|
if widget_id and (not isinstance(entry, dict) or canonical(entry.get("id")) != widget_id):
|
|
die(f"widget at {target_section}[{index}] is not {widget_id}")
|
|
del layout[target_section][index]
|
|
else:
|
|
if not widget_id:
|
|
die("widget id is required")
|
|
matches = find_entries(widget_id, target_section)
|
|
if not matches:
|
|
scope = f" in {target_section}" if target_section else ""
|
|
die(f"could not find widget {widget_id}{scope}")
|
|
if remove_all:
|
|
for section, index, _entry in reversed(matches):
|
|
del layout[section][index]
|
|
else:
|
|
section, index, _entry = matches[0]
|
|
del layout[section][index]
|
|
|
|
elif operation == "replace":
|
|
if not widget_id:
|
|
die("source widget id is required")
|
|
if not new_widget_id:
|
|
die("replacement widget id is required")
|
|
section, index, entry = source_location()
|
|
if not isinstance(entry, dict):
|
|
die("widget entry must be an object")
|
|
entry["id"] = new_widget_id
|
|
layout[section][index] = entry
|
|
|
|
elif operation == "set":
|
|
if not widget_id:
|
|
die("widget id is required")
|
|
if not set_key:
|
|
die("setting key is required")
|
|
if from_index != "":
|
|
section, index, entry = source_location()
|
|
elif target_index != "":
|
|
if not target_section:
|
|
die("--index requires --section for bar set")
|
|
index = parse_index(target_index)
|
|
entry = entry_at(target_section, index)
|
|
if not isinstance(entry, dict) or canonical(entry.get("id")) != widget_id:
|
|
die(f"widget at {target_section}[{index}] is not {widget_id}")
|
|
section = target_section
|
|
else:
|
|
matches = find_entries(widget_id, target_section)
|
|
if not matches:
|
|
scope = f" in {target_section}" if target_section else ""
|
|
die(f"could not find widget {widget_id}{scope}")
|
|
section, index, entry = matches[0]
|
|
if not isinstance(entry, dict):
|
|
die("widget entry must be an object")
|
|
if set_value_is_json:
|
|
try:
|
|
parsed_value = json.loads(set_value)
|
|
except json.JSONDecodeError as error:
|
|
die(f"invalid JSON value: {error}")
|
|
else:
|
|
parsed_value = set_value
|
|
layout[section][index][set_key] = parsed_value
|
|
|
|
else:
|
|
die(f"unknown operation: {operation}")
|
|
|
|
json.dump(config, sys.stdout, indent=2)
|
|
sys.stdout.write("\n")
|
|
PY
|
|
|
|
mv "$tmp" "$CONFIG_FILE"
|
|
omarchy-shell -q shell rescanPlugins >/dev/null 2>&1 || true
|
|
}
|
|
|
|
# Canonical ids of every known bar widget, read from manifests on disk
|
|
# (first-party + user). This is the same set `omarchy plugin list` reports, but
|
|
# read straight from disk so it works with the shell down and never races an
|
|
# in-flight rescan.
|
|
bar_widget_ids() {
|
|
require_command jq
|
|
first_party_manifest_paths | while IFS= read -r manifest; do
|
|
jq -r 'select((.kinds // []) | index("bar-widget")) | .id' "$manifest" 2>/dev/null
|
|
done
|
|
local user_dir="$HOME/.config/omarchy/plugins"
|
|
if [[ -d $user_dir ]]; then
|
|
find "$user_dir" -mindepth 1 -maxdepth 1 \( -type d -o -type l \) 2>/dev/null | while IFS= read -r dir; do
|
|
[[ -f $dir/manifest.json ]] || continue
|
|
jq -r 'select((.kinds // []) | index("bar-widget")) | .id' "$dir/manifest.json" 2>/dev/null
|
|
done
|
|
fi
|
|
}
|
|
|
|
bar_add() {
|
|
local id="${1:-}"
|
|
[[ -n $id ]] || fail "bar add requires a widget id"
|
|
shift
|
|
id=$(canonical_widget_id "$id")
|
|
# Only let real bar widgets into the layout — a typo'd or non-widget id just
|
|
# renders as a dead entry (this is how a stray "--help" once got in).
|
|
if ! bar_widget_ids | grep -qxF -- "$id"; then
|
|
fail "$id is not a known bar widget; run 'omarchy plugin list' to see valid ids"
|
|
fi
|
|
mutate_bar add --id "$id" "$@"
|
|
echo "Added $id to the bar"
|
|
}
|
|
|
|
bar_move() {
|
|
local id="${1:-}"
|
|
[[ -n $id ]] || fail "bar move requires a widget id"
|
|
shift
|
|
mutate_bar move --id "$id" "$@"
|
|
echo "Moved $id"
|
|
}
|
|
|
|
bar_remove() {
|
|
local id=""
|
|
if (( $# > 0 )) && [[ $1 != --* ]]; then
|
|
id="$1"
|
|
shift
|
|
fi
|
|
mutate_bar remove --id "$id" "$@"
|
|
if [[ -n $id ]]; then
|
|
echo "Removed $id from the bar"
|
|
else
|
|
echo "Removed bar widget"
|
|
fi
|
|
}
|
|
|
|
bar_set() {
|
|
local id="${1:-}"
|
|
local key="${2:-}"
|
|
local value="${3:-}"
|
|
[[ -n $id ]] || fail "bar set requires a widget id"
|
|
[[ -n $key ]] || fail "bar set requires a setting key"
|
|
(( $# >= 3 )) || fail "bar set requires a value"
|
|
shift 3
|
|
mutate_bar set --id "$id" --key "$key" --value "$value" "$@"
|
|
echo "Set $key on $(canonical_widget_id "$id")"
|
|
}
|
|
|
|
slug_id() {
|
|
tr '[:upper:]' '[:lower:]' <<<"$1" | sed -E 's/^omarchy\.//; s/[^a-z0-9]+/-/g; s/^-+//; s/-+$//'
|
|
}
|
|
|
|
first_party_manifest_paths() {
|
|
require_omarchy_path
|
|
find "$OMARCHY_PATH/shell/plugins" -mindepth 2 -maxdepth 3 -type f \( -name manifest.json -o -name '*.manifest.json' \) 2>/dev/null | sort
|
|
}
|
|
|
|
manifest_source_dir() {
|
|
local manifest="$1"
|
|
if [[ ${manifest##*/} == "manifest.json" ]]; then
|
|
dirname -- "$manifest"
|
|
else
|
|
dirname -- "$manifest"
|
|
fi
|
|
}
|
|
|
|
builtin_widget_info() {
|
|
require_command jq
|
|
local wanted
|
|
wanted=$(canonical_widget_id "$1")
|
|
|
|
local manifest id entry source_dir source_path name category multiple
|
|
while IFS= read -r manifest; do
|
|
if ! jq -e --arg id "$wanted" '
|
|
(.id == $id or ((.barWidget.aliases // []) | index($id))) and
|
|
((.kinds // []) | index("bar-widget"))
|
|
' "$manifest" >/dev/null 2>&1; then
|
|
continue
|
|
fi
|
|
|
|
id=$(jq -r '.id' "$manifest")
|
|
entry=$(jq -r '.entryPoints.barWidget // empty' "$manifest")
|
|
[[ -n $entry ]] || return 1
|
|
source_dir=$(manifest_source_dir "$manifest")
|
|
source_path="$source_dir/$entry"
|
|
name=$(jq -r '.barWidget.displayName // .name // .id' "$manifest")
|
|
category=$(jq -r '.barWidget.category // "Plugin"' "$manifest")
|
|
multiple=$(jq -r 'if .barWidget.allowMultiple == true then "true" else "false" end' "$manifest")
|
|
printf '%s\t%s\t%s\t%s\t%s\n' "$id" "$source_path" "$name" "$category" "$multiple"
|
|
return 0
|
|
done < <(first_party_manifest_paths)
|
|
|
|
return 1
|
|
}
|
|
|
|
clone_source_options() {
|
|
local id name
|
|
first_party_manifest_paths | while IFS= read -r manifest; do
|
|
id=$(jq -r '.id // empty' "$manifest" 2>/dev/null || true)
|
|
name=$(jq -r '.barWidget.displayName // .name // .id // empty' "$manifest" 2>/dev/null || true)
|
|
[[ -n $id ]] || continue
|
|
if jq -e '(.kinds // []) | index("bar-widget")' "$manifest" >/dev/null 2>&1; then
|
|
printf '%s\tBuilt-in widget\t%s\n' "$id" "${name:-$id}"
|
|
else
|
|
printf '%s\tBuilt-in plugin\t%s\n' "$id" "${name:-$id}"
|
|
fi
|
|
done
|
|
|
|
if [[ -d $HOME/.config/omarchy/plugins ]]; then
|
|
find "$HOME/.config/omarchy/plugins" -mindepth 1 -maxdepth 1 \( -type d -o -type l \) 2>/dev/null | sort | while IFS= read -r dir; do
|
|
[[ -f $dir/manifest.json ]] || continue
|
|
id=$(jq -r '.id // empty' "$dir/manifest.json" 2>/dev/null || true)
|
|
name=$(jq -r '.name // .id // empty' "$dir/manifest.json" 2>/dev/null || true)
|
|
[[ -n $id ]] && printf '%s\tUser plugin\t%s\n' "$id" "${name:-$id}"
|
|
done
|
|
fi
|
|
}
|
|
|
|
choose_value() {
|
|
local prompt="$1"
|
|
shift
|
|
if command -v gum >/dev/null 2>&1; then
|
|
gum choose --header "$prompt" "$@"
|
|
else
|
|
printf '%s\n' "$@" | fzf --prompt "$prompt > "
|
|
fi
|
|
}
|
|
|
|
input_value() {
|
|
local prompt="$1"
|
|
local value="${2:-}"
|
|
if command -v gum >/dev/null 2>&1; then
|
|
gum input --prompt "$prompt " --value "$value"
|
|
else
|
|
read -r -p "$prompt " value
|
|
printf '%s' "$value"
|
|
fi
|
|
}
|
|
|
|
choose_clone_source() {
|
|
local selected
|
|
if command -v gum >/dev/null 2>&1; then
|
|
selected=$(clone_source_options | awk -F '\t' '{ printf "%-32s %-15s %s\n", $3, $2, $1 }' | gum filter --header "Clone plugin" --placeholder "Search built-in and user plugins..." --limit 1) || return 1
|
|
awk '{ print $NF }' <<<"$selected"
|
|
elif command -v fzf >/dev/null 2>&1; then
|
|
selected=$(clone_source_options | fzf --delimiter=$'\t' --with-nth=1,2,3 --prompt "Clone plugin > ") || return 1
|
|
cut -f1 <<<"$selected"
|
|
else
|
|
fail "gum or fzf is required for interactive clone selection"
|
|
fi
|
|
}
|
|
|
|
edit_source_options() {
|
|
if [[ -d $HOME/.config/omarchy/plugins ]]; then
|
|
find "$HOME/.config/omarchy/plugins" -mindepth 1 -maxdepth 1 \( -type d -o -type l \) 2>/dev/null | sort | while IFS= read -r dir; do
|
|
[[ -f $dir/manifest.json ]] || continue
|
|
id=$(jq -r '.id // empty' "$dir/manifest.json" 2>/dev/null || true)
|
|
name=$(jq -r '.name // .id // empty' "$dir/manifest.json" 2>/dev/null || true)
|
|
[[ -n $id ]] && printf '%s\t%s\t%s\n' "$id" "${name:-$id}" "$dir"
|
|
done
|
|
fi
|
|
}
|
|
|
|
choose_edit_source() {
|
|
local selected
|
|
if command -v gum >/dev/null 2>&1; then
|
|
selected=$(edit_source_options | awk -F '\t' '{ printf "%-32s %s\n", $2, $1 }' | gum filter --header "Edit plugin" --placeholder "Search user plugins..." --limit 1) || return 1
|
|
awk '{ print $NF }' <<<"$selected"
|
|
elif command -v fzf >/dev/null 2>&1; then
|
|
selected=$(edit_source_options | fzf --delimiter=$'\t' --with-nth=2,1 --prompt "Edit plugin > ") || return 1
|
|
cut -f1 <<<"$selected"
|
|
else
|
|
fail "gum or fzf is required for interactive plugin selection"
|
|
fi
|
|
}
|
|
|
|
validate_user_plugin_dir() {
|
|
local dir="$1"
|
|
local requested_id="${2:-}"
|
|
local root="$HOME/.config/omarchy/plugins"
|
|
local dir_abs root_abs manifest_id
|
|
|
|
require_command jq
|
|
require_command python3
|
|
[[ -d $dir ]] || fail "plugin directory not found: $dir"
|
|
|
|
dir_abs=$(python3 -c 'from pathlib import Path; import sys; print(Path(sys.argv[1]).expanduser().resolve(strict=False))' "$dir")
|
|
root_abs=$(python3 -c 'from pathlib import Path; import sys; print(Path(sys.argv[1]).expanduser().resolve(strict=False))' "$root")
|
|
[[ $dir_abs == $root_abs/* ]] || fail "plugin edit only opens user plugins under $root"
|
|
[[ -f $dir_abs/manifest.json ]] || fail "plugin directory missing manifest.json: $dir_abs"
|
|
|
|
manifest_id=$(jq -r '.id // empty' "$dir_abs/manifest.json" 2>/dev/null || true)
|
|
[[ -n $manifest_id ]] || fail "plugin manifest missing id: $dir_abs/manifest.json"
|
|
[[ $manifest_id != omarchy.* && $(canonical_widget_id "$manifest_id") == $manifest_id ]] || fail "$manifest_id is built in; clone it first with: omarchy plugin clone $manifest_id local.$(slug_id "$manifest_id")"
|
|
if [[ -n $requested_id && $manifest_id != $requested_id ]]; then
|
|
fail "plugin id mismatch: requested $requested_id but manifest declares $manifest_id"
|
|
fi
|
|
|
|
printf '%s' "$dir_abs"
|
|
}
|
|
|
|
plugin_dir_for_id() {
|
|
local id="$1"
|
|
|
|
if [[ $id == */* ]]; then
|
|
validate_user_plugin_dir "$id"
|
|
return
|
|
fi
|
|
|
|
id=$(canonical_widget_id "$id")
|
|
if builtin_widget_info "$id" >/dev/null 2>&1 || [[ $id == omarchy.* ]]; then
|
|
fail "$id is built in; clone it first with: omarchy plugin clone $id local.$(slug_id "$id")"
|
|
fi
|
|
|
|
validate_user_plugin_dir "$HOME/.config/omarchy/plugins/$id" "$id"
|
|
}
|
|
|
|
open_plugin_dir() {
|
|
local tool="$1"
|
|
local dir="$2"
|
|
local prompt="${3:-}"
|
|
|
|
case "$tool" in
|
|
"" | none)
|
|
printf 'Plugin directory: %s\n' "$dir"
|
|
return
|
|
;;
|
|
ai)
|
|
omarchy-launch-ai --path "$dir" --prompt "$prompt"
|
|
;;
|
|
editor)
|
|
omarchy-launch-editor "$dir"
|
|
;;
|
|
*)
|
|
fail "unknown edit target: $tool (use ai, editor, or none)"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
edit_command() {
|
|
local id=""
|
|
if (( $# > 0 )) && [[ $1 != --* ]]; then
|
|
id="$1"
|
|
shift
|
|
fi
|
|
|
|
local open_tool=""
|
|
local prompt=""
|
|
while (( $# > 0 )); do
|
|
case "$1" in
|
|
--with)
|
|
open_tool="${2:-}"
|
|
[[ -n $open_tool ]] || fail "--with requires a value"
|
|
shift 2
|
|
;;
|
|
--open)
|
|
open_tool="${2:-}"
|
|
[[ -n $open_tool ]] || fail "--open requires a value"
|
|
shift 2
|
|
;;
|
|
--prompt)
|
|
prompt="${2:-}"
|
|
shift 2
|
|
;;
|
|
-h | --help)
|
|
usage
|
|
return
|
|
;;
|
|
*)
|
|
fail "unknown edit option: $1"
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ -z $id ]]; then
|
|
if [[ -t 0 && -t 1 ]]; then
|
|
id=$(choose_edit_source) || fail "edit cancelled"
|
|
else
|
|
fail "plugin id is required"
|
|
fi
|
|
fi
|
|
|
|
local dir
|
|
dir=$(plugin_dir_for_id "$id")
|
|
|
|
if [[ -z $open_tool ]]; then
|
|
if [[ -t 0 && -t 1 ]]; then
|
|
open_tool=$(choose_value "Edit with" ai editor)
|
|
else
|
|
open_tool="editor"
|
|
fi
|
|
fi
|
|
|
|
open_plugin_dir "$open_tool" "$dir" "$prompt"
|
|
}
|
|
|
|
rewrite_cloned_qml() {
|
|
local file="$1"
|
|
local id="$2"
|
|
local source_path="$3"
|
|
python3 - "$file" "$id" "$source_path" <<'PY'
|
|
import pathlib
|
|
import re
|
|
import sys
|
|
path, plugin_id, source_path = sys.argv[1], sys.argv[2], pathlib.Path(sys.argv[3])
|
|
text = open(path, encoding="utf-8").read()
|
|
text = re.sub(r'moduleName:\s*"[^"]+"', f'moduleName: "{plugin_id}"', text, count=1)
|
|
|
|
|
|
def resolve_relative(match):
|
|
rel = match.group(1)
|
|
if rel.startswith(("../", "./")) or (not rel.startswith("/") and "://" not in rel):
|
|
resolved = (source_path.parent / rel).resolve()
|
|
return '"' + resolved.as_uri() + '"'
|
|
return match.group(0)
|
|
|
|
text = re.sub(r'Qt\.resolvedUrl\("([^"]+)"\)', resolve_relative, text)
|
|
|
|
# Indicators dynamically loads sibling indicator components via string
|
|
# concatenation, so the simple Qt.resolvedUrl("literal") rewrite above cannot
|
|
# see it. Point the clone back at Omarchy's bundled indicator directory.
|
|
if source_path.name == "Indicators.qml":
|
|
indicators_url = (source_path.parent.parent / "indicators").resolve().as_uri() + "/"
|
|
text = text.replace(
|
|
'Qt.resolvedUrl("../indicators/" + indicatorSlot.indicatorId + ".qml")',
|
|
f'"{indicators_url}" + indicatorSlot.indicatorId + ".qml"',
|
|
)
|
|
|
|
# Panel-backed clones should not register the same global IPC target as the
|
|
# built-in widget. Direct panel clones can disable Panel.manageIpc themselves;
|
|
# wrapper widgets such as Weather disable it on the loaded panel instance.
|
|
if "ipcTarget:" in text and "manageIpc:" not in text:
|
|
text = text.replace(" id: root\n", " id: root\n manageIpc: false\n", 1)
|
|
if "function injectPanel()" in text and '"manageIpc" in target' not in text:
|
|
text = text.replace(
|
|
" if (!target) return\n",
|
|
" if (!target) return\n if (\"manageIpc\" in target) target.manageIpc = false\n",
|
|
1,
|
|
)
|
|
|
|
open(path, "w", encoding="utf-8").write(text)
|
|
PY
|
|
}
|
|
|
|
write_clone_manifest() {
|
|
local file="$1"
|
|
local id="$2"
|
|
local name="$3"
|
|
local description="$4"
|
|
local category="$5"
|
|
local multiple="$6"
|
|
local source_id="$7"
|
|
local source_path="$8"
|
|
python3 - "$file" "$id" "$name" "$description" "$category" "$multiple" "$source_id" "$source_path" <<'PY'
|
|
import json
|
|
import sys
|
|
file, plugin_id, name, description, category, multiple, source_id, source_path = sys.argv[1:]
|
|
data = {
|
|
"schemaVersion": 1,
|
|
"id": plugin_id,
|
|
"name": name,
|
|
"version": "1.0.0",
|
|
"author": "Local",
|
|
"description": description,
|
|
"kinds": ["bar-widget"],
|
|
"entryPoints": {"barWidget": "Widget.qml"},
|
|
"barWidget": {
|
|
"displayName": name,
|
|
"description": description,
|
|
"category": category,
|
|
"allowMultiple": multiple == "true"
|
|
},
|
|
"omarchy": {
|
|
"clonedFrom": source_id,
|
|
"clonedFromPath": source_path
|
|
}
|
|
}
|
|
open(file, "w", encoding="utf-8").write(json.dumps(data, indent=2) + "\n")
|
|
PY
|
|
}
|
|
|
|
update_cloned_manifest() {
|
|
local file="$1"
|
|
local id="$2"
|
|
local name="$3"
|
|
local source_id="$4"
|
|
local source_path="$5"
|
|
python3 - "$file" "$id" "$name" "$source_id" "$source_path" <<'PY'
|
|
import json
|
|
import sys
|
|
file, plugin_id, name, source_id, source_path = sys.argv[1:]
|
|
with open(file, encoding="utf-8") as handle:
|
|
data = json.load(handle)
|
|
data["id"] = plugin_id
|
|
data["name"] = name
|
|
if "barWidget" in data and isinstance(data["barWidget"], dict):
|
|
data["barWidget"]["displayName"] = name
|
|
meta = data.get("omarchy") if isinstance(data.get("omarchy"), dict) else {}
|
|
meta.update({"clonedFrom": source_id, "clonedFromPath": source_path})
|
|
data["omarchy"] = meta
|
|
open(file, "w", encoding="utf-8").write(json.dumps(data, indent=2) + "\n")
|
|
PY
|
|
}
|
|
|
|
clone_command() {
|
|
require_command jq
|
|
require_command python3
|
|
require_omarchy_path
|
|
|
|
local source_id=""
|
|
local new_id=""
|
|
if (( $# > 0 )) && [[ $1 != --* ]]; then
|
|
source_id="$1"
|
|
shift
|
|
fi
|
|
if (( $# > 0 )) && [[ $1 != --* ]]; then
|
|
new_id="$1"
|
|
shift
|
|
fi
|
|
|
|
local display_name=""
|
|
local open_tool=""
|
|
local prompt=""
|
|
local replace="false"
|
|
local add="false"
|
|
local use_bar="false"
|
|
local placement=()
|
|
|
|
while (( $# > 0 )); do
|
|
case "$1" in
|
|
--name)
|
|
display_name="${2:-}"
|
|
[[ -n $display_name ]] || fail "--name requires a value"
|
|
shift 2
|
|
;;
|
|
--with)
|
|
open_tool="${2:-}"
|
|
[[ -n $open_tool ]] || fail "--with requires a value"
|
|
shift 2
|
|
;;
|
|
--open)
|
|
open_tool="${2:-}"
|
|
[[ -n $open_tool ]] || fail "--open requires a value"
|
|
shift 2
|
|
;;
|
|
--prompt)
|
|
prompt="${2:-}"
|
|
shift 2
|
|
;;
|
|
--replace)
|
|
replace="true"
|
|
shift
|
|
;;
|
|
--add)
|
|
add="true"
|
|
shift
|
|
;;
|
|
--use)
|
|
use_bar="true"
|
|
shift
|
|
;;
|
|
--section | --index | --before | --after)
|
|
placement+=("$1" "${2:-}")
|
|
shift 2
|
|
;;
|
|
-h | --help)
|
|
usage
|
|
return
|
|
;;
|
|
*)
|
|
fail "unknown clone option: $1"
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ -z $source_id ]]; then
|
|
if [[ -t 0 && -t 1 ]]; then
|
|
source_id=$(choose_clone_source) || fail "clone cancelled"
|
|
else
|
|
fail "clone source is required"
|
|
fi
|
|
fi
|
|
source_id=$(canonical_widget_id "$source_id")
|
|
|
|
local default_slug
|
|
default_slug=$(slug_id "$source_id")
|
|
if [[ -z $new_id ]]; then
|
|
if [[ -t 0 && -t 1 ]]; then
|
|
new_id=$(input_value "New plugin id:" "local.$default_slug")
|
|
else
|
|
fail "new plugin id is required"
|
|
fi
|
|
fi
|
|
validate_plugin_id "$new_id"
|
|
|
|
local target_dir="$HOME/.config/omarchy/plugins/$new_id"
|
|
[[ ! -e $target_dir && ! -L $target_dir ]] || fail "$target_dir already exists"
|
|
|
|
local source_path=""
|
|
local source_name=""
|
|
local source_category="Plugin"
|
|
local source_multiple="false"
|
|
local source_type=""
|
|
|
|
if IFS=$'\t' read -r _ source_path source_name source_category source_multiple < <(builtin_widget_info "$source_id"); then
|
|
source_type="builtin-widget"
|
|
elif [[ -f $HOME/.config/omarchy/plugins/$source_id/manifest.json ]]; then
|
|
source_type="user-plugin"
|
|
source_path="$HOME/.config/omarchy/plugins/$source_id"
|
|
source_name=$(jq -r '.name // .id' "$source_path/manifest.json")
|
|
else
|
|
source_path=$(find "$OMARCHY_PATH/shell/plugins" -path '*/manifest.json' -print0 | xargs -0 jq -r --arg id "$source_id" 'select(.id == $id) | input_filename' 2>/dev/null | head -1 || true)
|
|
if [[ -n $source_path ]]; then
|
|
source_type="builtin-plugin"
|
|
source_path=$(dirname "$source_path")
|
|
source_name=$(jq -r '.name // .id' "$source_path/manifest.json")
|
|
fi
|
|
fi
|
|
|
|
[[ -n $source_type ]] || fail "unknown clone source: $source_id"
|
|
if [[ -z $display_name ]]; then
|
|
if [[ -t 0 && -t 1 ]]; then
|
|
display_name=$(input_value "Display name:" "${source_name:-$new_id}")
|
|
else
|
|
display_name="${source_name:-$new_id}"
|
|
fi
|
|
fi
|
|
[[ -n $display_name ]] || fail "display name is required"
|
|
|
|
mkdir -p "$target_dir"
|
|
if [[ $source_type == "builtin-widget" ]]; then
|
|
cp "$source_path" "$target_dir/Widget.qml"
|
|
rewrite_cloned_qml "$target_dir/Widget.qml" "$new_id" "$source_path"
|
|
write_clone_manifest "$target_dir/manifest.json" "$new_id" "$display_name" "Cloned from $source_id" "$source_category" "$source_multiple" "$source_id" "$source_path"
|
|
else
|
|
cp -aL "$source_path/." "$target_dir/"
|
|
update_cloned_manifest "$target_dir/manifest.json" "$new_id" "$display_name" "$source_id" "$source_path"
|
|
fi
|
|
|
|
{
|
|
printf '# %s\n\n' "$display_name"
|
|
printf 'Cloned from `%s`.\n\n' "$source_id"
|
|
printf 'Source: `%s`\n\n' "$source_path"
|
|
printf 'The original remains built into Omarchy. Remove this plugin directory or swap\n'
|
|
printf 'your bar entry back to `%s` whenever you want to return to the built-in.\n' "$source_id"
|
|
} >"$target_dir/UPSTREAM.md"
|
|
|
|
omarchy-shell -q shell rescanPlugins >/dev/null 2>&1 || true
|
|
|
|
if [[ $replace == "true" ]]; then
|
|
mutate_bar replace --id "$source_id" --new-id "$new_id"
|
|
echo "Cloned $source_id to $new_id and replaced the first bar instance"
|
|
elif [[ $add == "true" ]]; then
|
|
bar_add "$new_id" "${placement[@]}"
|
|
echo "Cloned $source_id to $new_id"
|
|
elif [[ $use_bar == "true" ]]; then
|
|
omarchy-config-shell-bar use "$new_id"
|
|
echo "Cloned $source_id to $new_id and selected it as the active bar option"
|
|
else
|
|
echo "Cloned $source_id to $new_id"
|
|
fi
|
|
|
|
if [[ -z $open_tool && -t 0 && -t 1 ]]; then
|
|
open_tool=$(choose_value "Edit with" ai editor)
|
|
fi
|
|
if [[ -n $open_tool ]]; then
|
|
edit_command "$new_id" --with "$open_tool" --prompt "$prompt"
|
|
fi
|
|
}
|
|
|
|
bar_command() {
|
|
local command="${1:-list}"
|
|
[[ $# -gt 0 ]] && shift || true
|
|
|
|
case "$command" in
|
|
edit | settings)
|
|
(( $# == 0 )) || fail "bar settings does not take arguments"
|
|
exec omarchy-launch-bar-settings
|
|
;;
|
|
options | option)
|
|
exec omarchy-config-shell-bar options "$@"
|
|
;;
|
|
use)
|
|
exec omarchy-config-shell-bar use "$@"
|
|
;;
|
|
reset)
|
|
exec omarchy-config-shell-bar reset "$@"
|
|
;;
|
|
list)
|
|
bar_list "$@"
|
|
;;
|
|
add)
|
|
bar_add "$@"
|
|
;;
|
|
move)
|
|
bar_move "$@"
|
|
;;
|
|
remove | rm)
|
|
bar_remove "$@"
|
|
;;
|
|
set)
|
|
bar_set "$@"
|
|
;;
|
|
-h | --help | help)
|
|
usage
|
|
;;
|
|
*)
|
|
fail "unknown bar command: $command"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
command="${1:-}"
|
|
case "$command" in
|
|
list | ls)
|
|
shift
|
|
print_plugins "$@"
|
|
;;
|
|
rescan)
|
|
shift
|
|
rescan_plugins "$@"
|
|
;;
|
|
enable)
|
|
shift
|
|
plugin_enabled true "$@"
|
|
;;
|
|
disable)
|
|
shift
|
|
plugin_enabled false "$@"
|
|
;;
|
|
clone)
|
|
shift
|
|
clone_command "$@"
|
|
;;
|
|
edit)
|
|
shift
|
|
edit_command "$@"
|
|
;;
|
|
bar | widget | widgets)
|
|
shift
|
|
bar_command "$@"
|
|
;;
|
|
source | available | add | update | remove | validate)
|
|
# These live in sibling binaries (omarchy-plugin-<command>). The `omarchy`
|
|
# dispatcher normally routes straight to them; delegate here too so invoking
|
|
# this base binary directly matches the commands its --help advertises.
|
|
shift
|
|
sibling="$(dirname -- "${BASH_SOURCE[0]}")/omarchy-plugin-$command"
|
|
[[ -x $sibling ]] || fail "missing helper: omarchy-plugin-$command"
|
|
exec "$sibling" "$@"
|
|
;;
|
|
-h | --help | help | "")
|
|
usage
|
|
;;
|
|
*)
|
|
fail "unknown command: $command"
|
|
;;
|
|
esac
|