Files
omarchycn/bin/omarchy-plugin-clone
T
David Heinemeier HanssonandClaude Fable 5 1bc89105d2 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>
2026-08-01 13:13:39 -05:00

169 lines
4.5 KiB
Bash
Executable File

#!/bin/bash
# omarchy:summary=Clone a built-in Omarchy shell plugin into your own config
# omarchy:group=plugin
# omarchy:args=<source-id> [--edit]
set -euo pipefail
PLUGINS_DIR="$HOME/.config/omarchy/plugins"
fail() {
echo "omarchy-plugin-clone: $*" >&2
exit 1
}
copy_plugin() {
local source_dir="$1"
local manifest="$2"
local target_dir="$3"
if [[ ${manifest##*/} == "manifest.json" ]]; then
cp -aL "$source_dir/." "$target_dir/"
return
fi
cp -aL "$manifest" "$target_dir/manifest.json"
local source target source_pattern file
while IFS=$'\t' read -r source target; do
[[ $target != /* && $target != *".."* ]] || fail "invalid clone target path: $target"
if [[ -d $source_dir/$source ]]; then
mkdir -p "$target_dir/$target"
cp -aL "$source_dir/$source/." "$target_dir/$target/"
else
mkdir -p "$(dirname "$target_dir/$target")"
cp -aL "$source_dir/$source" "$target_dir/$target"
fi
if [[ $source != "$target" ]]; then
source_pattern=${source//./\\.}
while IFS= read -r -d '' file; do
sed -i "s|$source_pattern|$target|g" "$file"
done < <(rg --files-with-matches --null --fixed-strings "$source" "$target_dir")
fi
done < <(jq -r '
[
(.entryPoints[] | {source: ., target: .}),
(.omarchy.clonePaths[]? | {source: .source, target: .target})
] | unique_by(.target)[] | [.source, .target] | @tsv
' "$manifest")
}
update_manifest() {
local target_dir="$1"
local source_id="$2"
local new_id="$3"
local display_name="$4"
local manifest="$target_dir/manifest.json"
# Keep built-in ids inside the plugin code as stable IPC targets. The shell
# uses clonedFrom to route those calls to this local manifest id.
jq \
--arg id "$new_id" \
--arg name "$display_name" \
--arg sourceId "$source_id" '
.id = $id |
.name = $name |
if (.barWidget | type) == "object" then
.barWidget.displayName = $name
else
.
end |
.omarchy = (
(if (.omarchy | type) == "object" then .omarchy else {} end) +
{ clonedFrom: $sourceId }
) |
del(.omarchy.clonePaths)
' "$manifest" >"$manifest.tmp"
mv "$manifest.tmp" "$manifest"
}
usage() {
cat <<USAGE
Usage: omarchy plugin clone <source-id> [--edit]
Copies a built-in plugin into ~/.config/omarchy/plugins/<username>.<id>/ so you
can edit it freely. The clone keeps all of the source plugin's files and kinds
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
}
source_id=""
edit_clone=0
if (( $# > 0 )) && [[ $1 != --* ]]; then
source_id="$1"
shift
fi
while (( $# > 0 )); do
case "$1" in
--edit)
edit_clone=1
shift
;;
-h | --help)
usage
exit 0
;;
*)
fail "unknown clone option: $1"
;;
esac
done
[[ -n $source_id ]] || fail "clone source is required"
source_info=$(omarchy-plugin-catalog | jq -r --arg id "$source_id" '
.[] | select(.firstParty and .id == $id)
| [
.sourceDir,
.manifestPath,
(.name // .id)
] | @tsv
')
[[ -n $source_info ]] || fail "unknown built-in plugin: $source_id"
IFS=$'\t' read -r source_dir source_manifest source_name <<<"$source_info"
new_id="${USER:-$(id -un)}.${source_id#omarchy.}"
display_name="My $source_name"
target_dir="$PLUGINS_DIR/$new_id"
[[ ! -e $target_dir && ! -L $target_dir ]] || fail "$target_dir already exists"
mkdir -p "$PLUGINS_DIR"
stage=$(mktemp -d "$PLUGINS_DIR/.clone.XXXXXX")
clone_complete=0
cleanup() {
[[ -d ${stage:-} ]] && rm -rf "$stage"
(( clone_complete )) || rm -rf "$target_dir"
}
trap cleanup EXIT
copy_plugin "$source_dir" "$source_manifest" "$stage"
update_manifest "$stage" "$source_id" "$new_id" "$display_name"
mv "$stage" "$target_dir"
stage=""
omarchy-shell shell rescanPlugins >/dev/null
discovered=0
for (( attempt = 0; attempt < 40; attempt++ )); do
if omarchy-plugin-list --json | jq -e --arg id "$new_id" 'any(.[]; .id == $id)' >/dev/null; then
discovered=1
break
fi
sleep 0.05
done
(( discovered )) || fail "cloned plugin '$new_id' was not discovered"
omarchy-plugin-enable "$new_id" >/dev/null
clone_complete=1
omarchy-notification-send -g 󰐱 \
"Editing Cloned Plugin" \
"Original plugin has been replace by clone."
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