Replace the plugin package manager with plain git

A plugin is now just a git repo cloned into ~/.config/omarchy/plugins/<id>/.
That one idea replaces the entire homegrown package-manager half of the
plugin suite: trusted-source registry, clone cache, catalog scanning,
semver comparison, staging dirs, and timestamped backups — 1,025 lines
across five binaries whose jobs git already does.

Gone:

- omarchy-plugin-source: the trusted-repo registry (sources.json) and its
  clone cache under ~/.cache/omarchy/plugin-sources/. The trust decision
  now happens once, at add time, with the same unsandboxed-code warning.
- omarchy-plugin-scan + omarchy-plugin-available: the catalog machinery
  over cached clones. Discovery belongs on a web page, not in the CLI.
- omarchy-plugin-add: copying folders out of cached source clones with
  hand-rolled staging and .bak backups. Replaced by a git clone.
- omarchy-plugin-update: manifest version comparison via sort -V and
  re-installs. Replaced by fetch + diff + fast-forward; git is the version
  and git is the backup.
- omarchy-plugin-remove and omarchy-plugin-edit as separate binaries:
  folded into omarchy-plugin, much slimmer.

The consolidated omarchy-plugin now handles the full lifecycle:

- add <git-url>: warn, clone into a dot-prefixed staging dir (invisible
  to the plugin scanner), validate, then move into place named by the
  manifest id. Plugins land disabled — enabling is the single consent
  moment, replacing the old review-before-copy flow.
- update [id | --all]: fetch origin HEAD, show the diff (delta when
  available), confirm, fast-forward. Updates are code the shell will run,
  so the result is re-validated and rolled back to ORIG_HEAD if upstream
  turned invalid (e.g. smuggled a symlink).
- remove [id]: git checkouts are deleted outright since upstream keeps
  the history; hand-made plugin folders still get a backup, and dev
  symlinks are just unlinked.
- edit [id]: opens the user plugin directory in a shell.

All commands keep the interactive/unattended split: gum prompts in a
terminal, hard refusal without --yes otherwise, so scripts and agents
never hang on a hidden prompt.

Kept as siblings: omarchy-plugin-catalog (omarchy-bar reads it),
omarchy-plugin-validate (the security boundary, now pruning .git from its
symlink scan since installs are git checkouts), and omarchy-plugin-clone
(local development of built-in widgets, a separate concern).

Trade-offs accepted: one repo = one plugin (no more multi-plugin source
repos), and ref pinning or branch switching is no longer a CLI feature —
an installed plugin is a plain checkout, so that is ordinary git in the
plugin directory.

None of the removed machinery ever shipped: it existed only on this
branch, so there is no migration. The net effect is 11 scripts / 2,040
lines down to 4 scripts / 1,080 lines, and one less concept for users to
learn — everyone already knows what a git repo is.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
David Heinemeier Hansson
2026-07-02 21:47:33 -07:00
co-authored by Claude Fable 5
parent 60e0a2a0cd
commit 798d6af8b0
12 changed files with 390 additions and 1355 deletions
+17 -20
View File
@@ -93,43 +93,40 @@ The full schema lives in `services/PluginRegistry.qml`.
## Installing a third-party plugin
Plugins are distributed as **source repos**: a git repo where every top-level
folder is one plugin with its own `manifest.json`. Trust a repo, then install
individual plugins from it. Everything lands in `~/.config/omarchy/plugins/<id>/`.
A plugin is a **git repo** with a `manifest.json` at its root. Adding one
clones it straight into `~/.config/omarchy/plugins/<id>/` (named by the
manifest id); updating is a fast-forward pull of that checkout.
```bash
omarchy plugin source add https://github.com/owner/omarchy-plugins.git
omarchy plugin available # list what your sources offer
omarchy plugin add some-widget # validate, copy, offer to enable
omarchy plugin update some-widget # shows a diff of changes first
omarchy plugin add https://github.com/acme/omarchy-weather.git
omarchy plugin update acme.weather # fetches, shows a diff, fast-forwards
omarchy plugin update --all
omarchy plugin remove some-widget
omarchy plugin remove acme.weather
```
> ⚠️ **Plugins run as unsandboxed code inside `omarchy-shell`.** Adding a source
> and installing both warn you and let you review the manifest, the files, and
> (on update) a diff of the changes before anything is copied or enabled. Only
> trust sources and plugins whose code you are willing to run.
> ⚠️ **Plugins run as unsandboxed code inside `omarchy-shell`.** Adding warns
> you before cloning, plugins land disabled so you can review the code before
> enabling, and updates show a diff of the changes before touching anything.
> Only add repos whose code you are willing to run.
Each command is **interactive** when run bare in a terminal (gum/fzf pickers,
Each command is **interactive** when run bare in a terminal (gum pickers,
confirmation, a diff to review) and fully **non-interactive** when given
arguments. Pass `--yes` to skip every prompt — this is the path for scripts and
AI agents:
```bash
omarchy plugin source add <url> --as acme --yes
omarchy plugin add acme-weather --from acme --enable --yes
omarchy plugin add https://github.com/acme/omarchy-weather.git --enable --yes
omarchy plugin update --all --yes
```
Sources live in `~/.config/omarchy/plugins/sources.json`; their clones are
cached under `~/.cache/omarchy/plugin-sources/`. The installer never runs
plugin code, install hooks, or sudo — it only copies files and toggles enabled
state over shell IPC.
The installer never runs plugin code, install hooks, or sudo — it only clones
files, validates the manifest, and toggles enabled state over shell IPC. Since
an installed plugin is a plain git checkout, anything beyond add/update
(pinning a ref, switching branches) is ordinary git in the plugin directory.
### Installing by hand
You can still drop a plugin in without a source:
You can still drop a plugin in without git:
1. Put it in `~/.config/omarchy/plugins/<plugin-id>/` with a `manifest.json`
plus the QML referenced from its `entryPoints`.