Prefix plugin clones with the username so shared clones don't collide

Clones of omarchy.clock become dhh.clock instead of local.clock, so a
published clone carries its author's namespace. The clone command owns
the id derivation and gains --edit to open the result in $EDITOR, and
the shell exposes clonedFrom in listPlugins so the plugin menu no longer
reconstructs clone ids.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
David Heinemeier Hansson
2026-08-01 13:13:39 -05:00
co-authored by Claude Fable 5
parent 2050f28d55
commit 1bc89105d2
9 changed files with 79 additions and 57 deletions
+2 -4
View File
@@ -12,7 +12,7 @@ PLUGIN_ICON=$'\U000f0431'
case "${1:-}" in case "${1:-}" in
enable) filter='(.enabled | not)' ;; enable) filter='(.enabled | not)' ;;
disable) filter='.canDisable and .enabled' ;; disable) filter='.canDisable and .enabled' ;;
clone) filter='.firstParty and ((.id | sub("^omarchy\\."; "local.")) as $clone_id | ($plugins | map(.id) | index($clone_id)) == null)' ;; clone) filter='.firstParty and (.id as $id | ($plugins | map(.clonedFrom // "") | index($id)) == null)' ;;
remove) filter='(.firstParty | not)' ;; remove) filter='(.firstParty | not)' ;;
*) *)
echo "Usage: omarchy-menu-plugin <enable|disable|clone|remove>" >&2 echo "Usage: omarchy-menu-plugin <enable|disable|clone|remove>" >&2
@@ -40,10 +40,8 @@ id=$(awk -F'\t' -v label="$name" '$2 == label { print $3; exit }' <<<"$rows")
[[ -n $id ]] || exit 1 [[ -n $id ]] || exit 1
if [[ $1 == "clone" ]]; then if [[ $1 == "clone" ]]; then
new_id="local.${id#omarchy.}"
target="$HOME/.config/omarchy/plugins/$new_id"
omarchy-launch-floating-terminal-with-presentation \ omarchy-launch-floating-terminal-with-presentation \
"omarchy-plugin-clone $(printf '%q' "$id") && exec \$EDITOR $(printf '%q' "$target")" "omarchy-plugin-clone --edit $(printf '%q' "$id")"
elif [[ $1 == "remove" ]]; then elif [[ $1 == "remove" ]]; then
omarchy-launch-floating-terminal-with-presentation "omarchy-plugin-remove $(printf '%q' "$id")" omarchy-launch-floating-terminal-with-presentation "omarchy-plugin-remove $(printf '%q' "$id")"
else else
+17 -6
View File
@@ -2,7 +2,7 @@
# omarchy:summary=Clone a built-in Omarchy shell plugin into your own config # omarchy:summary=Clone a built-in Omarchy shell plugin into your own config
# omarchy:group=plugin # omarchy:group=plugin
# omarchy:args=<source-id> # omarchy:args=<source-id> [--edit]
set -euo pipefail set -euo pipefail
@@ -81,16 +81,19 @@ update_manifest() {
usage() { usage() {
cat <<USAGE cat <<USAGE
Usage: omarchy plugin clone <source-id> Usage: omarchy plugin clone <source-id> [--edit]
Copies a built-in plugin into ~/.config/omarchy/plugins/local.<id>/ so you can Copies a built-in plugin into ~/.config/omarchy/plugins/<username>.<id>/ so you
edit it freely. The clone keeps all of the source plugin's files and kinds and can edit it freely. The clone keeps all of the source plugin's files and kinds
uses "My <name>" as its display name, then replaces the built-in with the clone. and uses "My <name>" as its display name, then replaces the built-in with the
clone. The username prefix keeps the id yours, so a shared clone does not
collide with anyone else's. --edit opens the clone in \$EDITOR afterwards.
USAGE USAGE
} }
source_id="" source_id=""
edit_clone=0
if (( $# > 0 )) && [[ $1 != --* ]]; then if (( $# > 0 )) && [[ $1 != --* ]]; then
source_id="$1" source_id="$1"
shift shift
@@ -98,6 +101,10 @@ fi
while (( $# > 0 )); do while (( $# > 0 )); do
case "$1" in case "$1" in
--edit)
edit_clone=1
shift
;;
-h | --help) -h | --help)
usage usage
exit 0 exit 0
@@ -121,7 +128,7 @@ source_info=$(omarchy-plugin-catalog | jq -r --arg id "$source_id" '
[[ -n $source_info ]] || fail "unknown built-in plugin: $source_id" [[ -n $source_info ]] || fail "unknown built-in plugin: $source_id"
IFS=$'\t' read -r source_dir source_manifest source_name <<<"$source_info" IFS=$'\t' read -r source_dir source_manifest source_name <<<"$source_info"
new_id="local.${source_id#omarchy.}" new_id="${USER:-$(id -un)}.${source_id#omarchy.}"
display_name="My $source_name" display_name="My $source_name"
target_dir="$PLUGINS_DIR/$new_id" target_dir="$PLUGINS_DIR/$new_id"
[[ ! -e $target_dir && ! -L $target_dir ]] || fail "$target_dir already exists" [[ ! -e $target_dir && ! -L $target_dir ]] || fail "$target_dir already exists"
@@ -155,3 +162,7 @@ omarchy-notification-send -g 󰐱 \
"Editing Cloned Plugin" \ "Editing Cloned Plugin" \
"Original plugin has been replace by clone." "Original plugin has been replace by clone."
echo "Cloned $source_id to $target_dir and switched to $new_id" echo "Cloned $source_id to $target_dir and switched to $new_id"
if (( edit_clone )); then
# Word-splitting is deliberate: EDITOR may carry flags.
exec $EDITOR "$target_dir"
fi
+2 -2
View File
@@ -184,7 +184,7 @@ Clone it into the user plugin directory instead:
```bash ```bash
omarchy plugin clone omarchy.workspaces omarchy plugin clone omarchy.workspaces
# Edit ~/.config/omarchy/plugins/local.workspaces/; saved changes reload automatically. # Edit ~/.config/omarchy/plugins/<username>.workspaces/; saved changes reload automatically.
``` ```
**Commands:** `omarchy restart shell`, `omarchy refresh shell` **Commands:** `omarchy restart shell`, `omarchy refresh shell`
@@ -419,6 +419,6 @@ This skill intentionally does not cover Omarchy source development. Do not use t
- "Clear all reminders" -> `omarchy reminder clear` - "Clear all reminders" -> `omarchy reminder clear`
- "Customize the catppuccin theme colors" -> Create `~/.config/omarchy/themes/catppuccin-custom/` by copying from stock, then edit - "Customize the catppuccin theme colors" -> Create `~/.config/omarchy/themes/catppuccin-custom/` by copying from stock, then edit
- "Run a script every time I change themes" -> Install it with `omarchy hook install theme-set <script>` - "Run a script every time I change themes" -> Install it with `omarchy hook install theme-set <script>`
- "Change how workspace labels are rendered" -> Clone `omarchy.workspaces`, which switches the bar to `local.workspaces`, then edit the clone - "Change how workspace labels are rendered" -> Clone `omarchy.workspaces`, which switches the bar to `<username>.workspaces`, then edit the clone
- "Lock after ten minutes" -> Set `idle.lock` to `600` in `~/.config/omarchy/shell.json` - "Lock after ten minutes" -> Set `idle.lock` to `600` in `~/.config/omarchy/shell.json`
- "Reset shell/bar to defaults" -> `omarchy refresh shell` - "Reset shell/bar to defaults" -> `omarchy refresh shell`
+5 -4
View File
@@ -59,10 +59,11 @@ no checkout to delete. Add, Clone, and Remove open a terminal so their warning,
editor, confirmation, and output stay visible. editor, confirmation, and output stay visible.
Cloning `omarchy.clock`, for example, creates and switches to Cloning `omarchy.clock`, for example, creates and switches to
`~/.config/omarchy/plugins/local.clock/`, names it `My Clock`, and preserves `~/.config/omarchy/plugins/<username>.clock/` (e.g. `dhh.clock`), names it
the built-in IPC identity so existing shortcuts keep working. Saving files in `My Clock`, and preserves the built-in IPC identity so existing shortcuts keep
any installed plugin reloads its code automatically, and removing an active clone working. The username prefix keeps a shared clone from colliding with anyone
switches back to its built-in source. else's. Saving files in any installed plugin reloads its code automatically,
and removing an active clone switches back to its built-in source.
For a bar widget, on and off means its place in the bar. Everything else is For a bar widget, on and off means its place in the bar. Everything else is
loaded by default when it is built in, so `shell.json` records only the loaded by default when it is built in, so `shell.json` records only the
+5 -3
View File
@@ -143,15 +143,17 @@ The `omarchy plugin` commands wrap those calls. `omarchy bar move` and
To hack on a built-in plugin safely, clone it into user config instead of To hack on a built-in plugin safely, clone it into user config instead of
editing the built-in source. The complete plugin directory is copied, including editing the built-in source. The complete plugin directory is copied, including
every declared kind and local dependency. A built-in id such as every declared kind and local dependency. A built-in id such as
`omarchy.clock` becomes `local.clock`, with `My Clock` as its display name. `omarchy.clock` becomes `<username>.clock` (e.g. `dhh.clock`), with `My Clock`
as its display name. The username prefix keeps shared clones from colliding
with each other or with other plugin authors.
```bash ```bash
omarchy plugin clone omarchy.clock omarchy plugin clone omarchy.clock
``` ```
Cloning switches from the built-in to the new local plugin, preserving an Cloning switches from the built-in to the new personal plugin, preserving an
existing bar widget's position and settings. Setup > Plugins > Clone provides existing bar widget's position and settings. Setup > Plugins > Clone provides
the interactive picker, then opens the new `local.*` directory in `$EDITOR`. the interactive picker, then opens the new `<username>.*` directory in `$EDITOR`.
Existing shortcuts and shell IPC calls made to the built-in id are routed to Existing shortcuts and shell IPC calls made to the built-in id are routed to
the enabled clone, so cloning does not require changing its callers. Removing the enabled clone, so cloning does not require changing its callers. Removing
an active clone switches back to its built-in source. an active clone switches back to its built-in source.
+4 -1
View File
@@ -938,6 +938,8 @@ ShellRoot {
var isBarOption = Array.isArray(kinds) && kinds.indexOf("bar") !== -1 var isBarOption = Array.isArray(kinds) && kinds.indexOf("bar") !== -1
var isBarWidget = Array.isArray(kinds) && kinds.indexOf("bar-widget") !== -1 var isBarWidget = Array.isArray(kinds) && kinds.indexOf("bar-widget") !== -1
var active = isBarOption && shell.isActiveBarOption(id) var active = isBarOption && shell.isActiveBarOption(id)
var metadata = plugins[id].omarchy
var clonedFrom = Util.isPlainObject(metadata) ? String(metadata.clonedFrom || "") : ""
out.push({ out.push({
id: id, id: id,
name: plugins[id].name, name: plugins[id].name,
@@ -952,7 +954,8 @@ ShellRoot {
// that a caller offering the verbs does not have to read kinds and // that a caller offering the verbs does not have to read kinds and
// work it out again. // work it out again.
canDisable: !isBarOption, canDisable: !isBarOption,
firstParty: !!plugins[id].__isFirstParty firstParty: !!plugins[id].__isFirstParty,
clonedFrom: clonedFrom
}) })
} }
// Consumers should not each invent their own presentation order. // Consumers should not each invent their own presentation order.
+16 -15
View File
@@ -70,15 +70,15 @@ pick() {
cat >"$TMPDIR/plugins.json" <<'JSON' cat >"$TMPDIR/plugins.json" <<'JSON'
[ [
{"id": "omarchy.clock", "name": "Clock", "kinds": ["bar-widget"], "enabled": false, "active": false, "canDisable": true, "firstParty": true}, {"id": "omarchy.clock", "name": "Clock", "kinds": ["bar-widget"], "enabled": false, "active": false, "canDisable": true, "firstParty": true},
{"id": "local.clock", "name": "Clock", "kinds": ["bar-widget"], "enabled": false, "active": false, "canDisable": true, "firstParty": false} {"id": "tester.clock", "name": "Clock", "kinds": ["bar-widget"], "enabled": false, "active": false, "canDisable": true, "firstParty": false}
] ]
JSON JSON
pick enable "Clock (local.clock)" pick enable "Clock (tester.clock)"
[[ $ROWS == *"Clock (omarchy.clock)"* && $ROWS == *"Clock (local.clock)"* ]] \ [[ $ROWS == *"Clock (omarchy.clock)"* && $ROWS == *"Clock (tester.clock)"* ]] \
|| fail "picker tells two plugins of the same name apart" "$ROWS" || fail "picker tells two plugins of the same name apart" "$ROWS"
pass "picker tells two plugins of the same name apart" pass "picker tells two plugins of the same name apart"
[[ $CALLS == *"omarchy-plugin-enable local.clock"* ]] \ [[ $CALLS == *"omarchy-plugin-enable tester.clock"* ]] \
|| fail "picker acts on the row that was picked, not the one that shares its name" "$CALLS" || fail "picker acts on the row that was picked, not the one that shares its name" "$CALLS"
pass "picker acts on the row that was picked, not the one that shares its name" pass "picker acts on the row that was picked, not the one that shares its name"
@@ -87,19 +87,19 @@ pass "picker acts on the row that was picked, not the one that shares its name"
cat >"$TMPDIR/plugins.json" <<'JSON' cat >"$TMPDIR/plugins.json" <<'JSON'
[ [
{"id": "omarchy.clock", "name": "Clock", "kinds": ["bar-widget"], "enabled": true, "active": false, "canDisable": true, "firstParty": true}, {"id": "omarchy.clock", "name": "Clock", "kinds": ["bar-widget"], "enabled": true, "active": false, "canDisable": true, "firstParty": true},
{"id": "local.clock", "name": "Clock", "kinds": ["bar-widget"], "enabled": false, "active": false, "canDisable": true, "firstParty": false} {"id": "tester.clock", "name": "Clock", "kinds": ["bar-widget"], "enabled": false, "active": false, "canDisable": true, "firstParty": false}
] ]
JSON JSON
pick enable "Clock" pick enable "Clock"
[[ $ROWS != *"("* ]] || fail "picker adorns a row only when its name is taken twice over" "$ROWS" [[ $ROWS != *"("* ]] || fail "picker adorns a row only when its name is taken twice over" "$ROWS"
pass "picker adorns a row only when its name is taken twice over" pass "picker adorns a row only when its name is taken twice over"
[[ $CALLS == *"omarchy-plugin-enable local.clock"* ]] \ [[ $CALLS == *"omarchy-plugin-enable tester.clock"* ]] \
|| fail "picker resolves a lone row to the plugin the verb offered, not a namesake it filtered out" "$CALLS" || fail "picker resolves a lone row to the plugin the verb offered, not a namesake it filtered out" "$CALLS"
pass "picker resolves a lone row to the plugin the verb offered, not a namesake it filtered out" pass "picker resolves a lone row to the plugin the verb offered, not a namesake it filtered out"
pick remove "Clock" pick remove "Clock"
[[ $CALLS == *"omarchy-plugin-remove local.clock"* ]] \ [[ $CALLS == *"omarchy-plugin-remove tester.clock"* ]] \
|| fail "picker removes the plugin whose row was picked" "$CALLS" || fail "picker removes the plugin whose row was picked" "$CALLS"
pass "picker removes the plugin whose row was picked" pass "picker removes the plugin whose row was picked"
@@ -118,8 +118,8 @@ pass "picker leaves an unambiguous name unadorned"
|| fail "picker delegates plugin enablement to the plugin command" "$CALLS" || fail "picker delegates plugin enablement to the plugin command" "$CALLS"
pass "picker delegates plugin enablement to the plugin command" pass "picker delegates plugin enablement to the plugin command"
# Clone offers only first-party plugins without an existing local counterpart, # Clone offers only first-party plugins that no installed clone points back at,
# then performs the clone and opens its deterministic path in $EDITOR. # then hands the pick to the clone command, which opens the result in $EDITOR.
cat >"$TMPDIR/plugins.json" <<'JSON' cat >"$TMPDIR/plugins.json" <<'JSON'
[ [
{"id": "omarchy.clock", "name": "Clock", "kinds": ["bar-widget"], "enabled": true, "active": false, "canDisable": true, "firstParty": true}, {"id": "omarchy.clock", "name": "Clock", "kinds": ["bar-widget"], "enabled": true, "active": false, "canDisable": true, "firstParty": true},
@@ -131,15 +131,16 @@ pick clone "Clock"
[[ $ROWS == *"Clock"* && $ROWS != *"Weather"* ]] || [[ $ROWS == *"Clock"* && $ROWS != *"Weather"* ]] ||
fail "clone picker offers only built-in plugins" "$ROWS" fail "clone picker offers only built-in plugins" "$ROWS"
pass "clone picker offers built-in plugins" pass "clone picker offers built-in plugins"
[[ $CALLS == *'terminal: omarchy-plugin-clone omarchy.clock && exec $EDITOR '*"/.config/omarchy/plugins/local.clock" ]] || [[ $CALLS == *"terminal: omarchy-plugin-clone --edit omarchy.clock"* ]] ||
fail "clone picker opens the cloned path in EDITOR" "$CALLS" fail "clone picker delegates cloning and editing to the clone command" "$CALLS"
pass "clone picker clones and opens the local plugin" pass "clone picker clones and opens the personal plugin"
# Once local.<id> is discovered, the source no longer belongs in Clone. # Once a clone pointing back at the source is discovered, whatever it is named,
# the source no longer belongs in Clone.
cat >"$TMPDIR/plugins.json" <<'JSON' cat >"$TMPDIR/plugins.json" <<'JSON'
[ [
{"id": "omarchy.clock", "name": "Clock", "kinds": ["bar-widget"], "enabled": true, "active": false, "canDisable": true, "firstParty": true}, {"id": "omarchy.clock", "name": "Clock", "kinds": ["bar-widget"], "enabled": true, "active": false, "canDisable": true, "firstParty": true},
{"id": "local.clock", "name": "My Clock", "kinds": ["bar-widget"], "enabled": false, "active": false, "canDisable": true, "firstParty": false} {"id": "tester.clock", "name": "My Clock", "kinds": ["bar-widget"], "enabled": false, "active": false, "canDisable": true, "firstParty": false, "clonedFrom": "omarchy.clock"}
] ]
JSON JSON
@@ -179,7 +180,7 @@ pass "picker keeps a bar out of disable"
cat >"$TMPDIR/plugins.json" <<'JSON' cat >"$TMPDIR/plugins.json" <<'JSON'
[ [
{"id": "omarchy.bar", "name": "Bar", "kinds": ["bar"], "enabled": false, "active": false, "canDisable": false, "firstParty": true}, {"id": "omarchy.bar", "name": "Bar", "kinds": ["bar"], "enabled": false, "active": false, "canDisable": false, "firstParty": true},
{"id": "local.neon-bar", "name": "Neon Bar", "kinds": ["bar"], "enabled": true, "active": true, "canDisable": false, "firstParty": false} {"id": "tester.neon-bar", "name": "Neon Bar", "kinds": ["bar"], "enabled": true, "active": true, "canDisable": false, "firstParty": false}
] ]
JSON JSON
+27 -21
View File
@@ -31,7 +31,7 @@ fi
exit 0 exit 0
SH SH
for command in omarchy-plugin-enable omarchy-notification-send; do for command in omarchy-plugin-enable omarchy-notification-send fake-editor; do
cat >"$TMPDIR/bin/$command" <<'SH' cat >"$TMPDIR/bin/$command" <<'SH'
#!/bin/bash #!/bin/bash
printf '%s %s\n' "${0##*/}" "$*" >>"$FAKE_CALLS" printf '%s %s\n' "${0##*/}" "$*" >>"$FAKE_CALLS"
@@ -49,14 +49,14 @@ clone_plugin() {
} }
} }
}' }'
HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" PATH="$TMPDIR/bin:$ROOT/bin:$PATH" \ HOME="$TMPDIR/home" USER=tester OMARCHY_PATH="$ROOT" PATH="$TMPDIR/bin:$ROOT/bin:$PATH" \
FAKE_CALLS="$CALLS" OMARCHY_TEST_ROOT="$ROOT" \ FAKE_CALLS="$CALLS" OMARCHY_TEST_ROOT="$ROOT" \
FAKE_SHELL_CONFIG="${FAKE_CLONE_CONFIG:-$default_config}" \ FAKE_SHELL_CONFIG="${FAKE_CLONE_CONFIG:-$default_config}" \
omarchy-plugin-clone "$@" omarchy-plugin-clone "$@"
} }
clone_plugin omarchy.clock >/dev/null clone_plugin omarchy.clock >/dev/null
clock="$TMPDIR/home/.config/omarchy/plugins/local.clock" clock="$TMPDIR/home/.config/omarchy/plugins/tester.clock"
for file in manifest.json BarWidget.qml Panel.qml Model.js; do for file in manifest.json BarWidget.qml Panel.qml Model.js; do
[[ -f $clock/$file ]] || fail "clock clone is missing $file" [[ -f $clock/$file ]] || fail "clock clone is missing $file"
@@ -73,7 +73,7 @@ rg -qF "omarchy.clock" "$clock" -g '*.qml' -g '*.js' ||
pass "clone preserves the built-in runtime IPC id" pass "clone preserves the built-in runtime IPC id"
jq -e ' jq -e '
.id == "local.clock" and .id == "tester.clock" and
.name == "My Clock" and .name == "My Clock" and
.barWidget.displayName == "My Clock" and .barWidget.displayName == "My Clock" and
.omarchy.clonedFrom == "omarchy.clock" and .omarchy.clonedFrom == "omarchy.clock" and
@@ -82,53 +82,53 @@ jq -e '
' "$clock/manifest.json" >/dev/null || fail "clock clone manifest is incorrect" ' "$clock/manifest.json" >/dev/null || fail "clock clone manifest is incorrect"
pass "clone updates identity without replacing the manifest" pass "clone updates identity without replacing the manifest"
grep -qx 'omarchy-plugin-enable local.clock' "$CALLS" || grep -qx 'omarchy-plugin-enable tester.clock' "$CALLS" ||
fail "clone does not enable the editable copy" fail "clone does not enable the editable copy"
grep -qx 'omarchy-notification-send -g 󰐱 Editing Cloned Plugin Original plugin has been replace by clone.' "$CALLS" || grep -qx 'omarchy-notification-send -g 󰐱 Editing Cloned Plugin Original plugin has been replace by clone.' "$CALLS" ||
fail "clone does not notify that the editable clone is active" fail "clone does not notify that the editable clone is active"
pass "clone enables bar widgets and confirms the editable clone" pass "clone enables bar widgets and confirms the editable clone"
clone_plugin omarchy.keyboard-layout >/dev/null clone_plugin omarchy.keyboard-layout >/dev/null
grep -qx 'omarchy-plugin-enable local.keyboard-layout' "$CALLS" || grep -qx 'omarchy-plugin-enable tester.keyboard-layout' "$CALLS" ||
fail "clone does not enable a clone of a legacy string-form bar entry" fail "clone does not enable a clone of a legacy string-form bar entry"
pass "clone enables clones of legacy string-form bar entries" pass "clone enables clones of legacy string-form bar entries"
clone_plugin omarchy.menu >/dev/null clone_plugin omarchy.menu >/dev/null
menu="$TMPDIR/home/.config/omarchy/plugins/local.menu" menu="$TMPDIR/home/.config/omarchy/plugins/tester.menu"
for file in manifest.json Menu.qml MenuModel.js BarWidget.qml; do for file in manifest.json Menu.qml MenuModel.js BarWidget.qml; do
[[ -f $menu/$file ]] || fail "menu clone is missing $file" [[ -f $menu/$file ]] || fail "menu clone is missing $file"
done done
jq -e ' jq -e '
.id == "local.menu" and .id == "tester.menu" and
.kinds == ["menu", "bar-widget"] and .kinds == ["menu", "bar-widget"] and
.entryPoints.menu == "Menu.qml" and .entryPoints.menu == "Menu.qml" and
.entryPoints.barWidget == "BarWidget.qml" .entryPoints.barWidget == "BarWidget.qml"
' "$menu/manifest.json" >/dev/null || fail "menu clone loses plugin kinds" ' "$menu/manifest.json" >/dev/null || fail "menu clone loses plugin kinds"
grep -qx 'omarchy-plugin-enable local.menu' "$CALLS" || grep -qx 'omarchy-plugin-enable tester.menu' "$CALLS" ||
fail "clone does not enable a multi-kind plugin" fail "clone does not enable a multi-kind plugin"
pass "clone preserves and enables multi-kind plugins" pass "clone preserves and enables multi-kind plugins"
remove_output=$(HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" PATH="$TMPDIR/bin:$ROOT/bin:$PATH" \ remove_output=$(HOME="$TMPDIR/home" OMARCHY_PATH="$ROOT" PATH="$TMPDIR/bin:$ROOT/bin:$PATH" \
FAKE_CALLS="$CALLS" OMARCHY_TEST_ROOT="$ROOT" \ FAKE_CALLS="$CALLS" OMARCHY_TEST_ROOT="$ROOT" \
omarchy-plugin-remove local.menu --yes) omarchy-plugin-remove tester.menu --yes)
grep -qx 'omarchy-shell shell setPluginEnabled local.menu false' "$CALLS" || grep -qx 'omarchy-shell shell setPluginEnabled tester.menu false' "$CALLS" ||
fail "removing an enabled clone does not disable it first" fail "removing an enabled clone does not disable it first"
grep -q 'Restored omarchy.menu.' <<<"$remove_output" || grep -q 'Restored omarchy.menu.' <<<"$remove_output" ||
fail "removing a clone does not report its restored source" fail "removing a clone does not report its restored source"
pass "removing an enabled clone goes through plugin disable and reports its source" pass "removing an enabled clone goes through plugin disable and reports its source"
clone_plugin omarchy.active-window >/dev/null clone_plugin omarchy.active-window >/dev/null
[[ -f $TMPDIR/home/.config/omarchy/plugins/local.active-window/ActiveWindow.qml ]] || [[ -f $TMPDIR/home/.config/omarchy/plugins/tester.active-window/ActiveWindow.qml ]] ||
fail "flat bar plugin clone is incomplete" fail "flat bar plugin clone is incomplete"
pass "flat bar plugins clone from adjacent manifests" pass "flat bar plugins clone from adjacent manifests"
grep -qx 'omarchy-plugin-enable local.active-window' "$CALLS" || grep -qx 'omarchy-plugin-enable tester.active-window' "$CALLS" ||
fail "clone does not activate a bar widget whose source is absent" fail "clone does not activate a bar widget whose source is absent"
pass "clone activates an absent bar widget" pass "clone activates an absent bar widget"
clone_plugin omarchy.indicators >/dev/null clone_plugin omarchy.indicators >/dev/null
indicators="$TMPDIR/home/.config/omarchy/plugins/local.indicators" indicators="$TMPDIR/home/.config/omarchy/plugins/tester.indicators"
for file in Indicators.qml indicators/Dnd.qml indicators/Reminder.qml; do for file in Indicators.qml indicators/Dnd.qml indicators/Reminder.qml; do
[[ -f $indicators/$file ]] || fail "indicators clone is missing $file" [[ -f $indicators/$file ]] || fail "indicators clone is missing $file"
done done
@@ -137,20 +137,26 @@ grep -q 'Qt.resolvedUrl("indicators/"' "$indicators/Indicators.qml" ||
pass "flat bar plugins declare extra clone dependencies" pass "flat bar plugins declare extra clone dependencies"
clone_plugin omarchy.tray >/dev/null clone_plugin omarchy.tray >/dev/null
[[ -f $TMPDIR/home/.config/omarchy/plugins/local.tray/TrayModel.js ]] || [[ -f $TMPDIR/home/.config/omarchy/plugins/tester.tray/TrayModel.js ]] ||
fail "tray clone is missing its model" fail "tray clone is missing its model"
pass "flat bar plugins keep local script dependencies" pass "flat bar plugins keep local script dependencies"
clone_plugin omarchy.bar >/dev/null clone_plugin omarchy.bar >/dev/null
grep -qx 'omarchy-plugin-enable local.bar' "$CALLS" || grep -qx 'omarchy-plugin-enable tester.bar' "$CALLS" ||
fail "clone does not select a cloned bar" fail "clone does not select a cloned bar"
pass "clone switches full bars" pass "clone switches full bars"
clone_plugin omarchy.background >/dev/null clone_plugin omarchy.background >/dev/null
grep -qx 'omarchy-plugin-enable local.background' "$CALLS" || grep -qx 'omarchy-plugin-enable tester.background' "$CALLS" ||
fail "clone does not enable an ordinary cloned plugin" fail "clone does not enable an ordinary cloned plugin"
pass "clone switches ordinary plugins" pass "clone switches ordinary plugins"
EDITOR=fake-editor clone_plugin omarchy.weather --edit >/dev/null
grep -qx "fake-editor $TMPDIR/home/.config/omarchy/plugins/tester.weather" "$CALLS" ||
fail "clone --edit does not open the clone in EDITOR"
pass "clone --edit opens the clone in EDITOR"
rm -rf "$TMPDIR/home/.config/omarchy/plugins/tester.weather"
mkdir -p "$TMPDIR/home/.config/omarchy/plugins/acme.example" mkdir -p "$TMPDIR/home/.config/omarchy/plugins/acme.example"
cat >"$TMPDIR/home/.config/omarchy/plugins/acme.example/manifest.json" <<'JSON' cat >"$TMPDIR/home/.config/omarchy/plugins/acme.example/manifest.json" <<'JSON'
{"id":"acme.example","name":"Example","kinds":["bar-widget"],"entryPoints":{"barWidget":"Widget.qml"}} {"id":"acme.example","name":"Example","kinds":["bar-widget"],"entryPoints":{"barWidget":"Widget.qml"}}
@@ -163,14 +169,14 @@ pass "clone is limited to built-in plugins"
if clone_plugin omarchy.weather custom.weather >/dev/null 2>&1; then if clone_plugin omarchy.weather custom.weather >/dev/null 2>&1; then
fail "clone accepts a custom id" fail "clone accepts a custom id"
fi fi
[[ ! -e $TMPDIR/home/.config/omarchy/plugins/local.weather ]] || [[ ! -e $TMPDIR/home/.config/omarchy/plugins/tester.weather ]] ||
fail "rejected custom id leaves a clone behind" fail "rejected custom id leaves a clone behind"
pass "clone derives the local id" pass "clone derives the personal id from the username"
if clone_plugin omarchy.weather --replace >/dev/null 2>&1; then if clone_plugin omarchy.weather --replace >/dev/null 2>&1; then
fail "clone still accepts bar layout actions" fail "clone still accepts bar layout actions"
fi fi
[[ ! -e $TMPDIR/home/.config/omarchy/plugins/local.weather ]] || [[ ! -e $TMPDIR/home/.config/omarchy/plugins/tester.weather ]] ||
fail "rejected bar action leaves a clone behind" fail "rejected bar action leaves a clone behind"
pass "clone does not accept manual switch options" pass "clone does not accept manual switch options"
@@ -182,6 +188,6 @@ pass "clone requires an explicit source id"
if FAKE_NO_DISCOVERY=1 clone_plugin omarchy.osd >/dev/null 2>&1; then if FAKE_NO_DISCOVERY=1 clone_plugin omarchy.osd >/dev/null 2>&1; then
fail "clone succeeds before the shell discovers it" fail "clone succeeds before the shell discovers it"
fi fi
[[ ! -e $TMPDIR/home/.config/omarchy/plugins/local.osd ]] || [[ ! -e $TMPDIR/home/.config/omarchy/plugins/tester.osd ]] ||
fail "failed clone discovery leaves a partial clone behind" fail "failed clone discovery leaves a partial clone behind"
pass "clone removes a partial clone when switching fails" pass "clone removes a partial clone when switching fails"
+1 -1
View File
@@ -132,7 +132,7 @@ done
jq -e ' jq -e '
map(.id) as $ids | map(.id) as $ids |
all(["omarchy.menu", "omarchy.notifications", "omarchy.clock", "omarchy.osd"][]; $ids | index(.)) and all(["omarchy.menu", "omarchy.notifications", "omarchy.clock", "omarchy.osd"][]; $ids | index(.)) and
all(.[]; (.kinds | type == "array") and (.enabled | type == "boolean") and (.canDisable | type == "boolean") and (.firstParty | type == "boolean")) and all(.[]; (.kinds | type == "array") and (.enabled | type == "boolean") and (.canDisable | type == "boolean") and (.firstParty | type == "boolean") and (.clonedFrom | type == "string")) and
([.[].name] == ([.[].name] | sort)) ([.[].name] == ([.[].name] | sort))
' <<<"$plugins" >/dev/null || { ' <<<"$plugins" >/dev/null || {
printf 'Plugins:\n%s\n' "$plugins" | jq . >&2 printf 'Plugins:\n%s\n' "$plugins" | jq . >&2