* Share the git URL check between theme-install and plugin-add
Both commands clone a URL a stranger can choose, and each carried its own copy of the rule that refuses a git option or a `<helper>::<address>` transport helper before cloning. Two copies of a security check drift: the second one arrived four months after the first, and only because someone went looking for it.
The rule now lives in omarchy-git-url-check and the callers ask it. Its absence refuses the URL rather than waving it through, since the callers read a non-zero status as a refusal and a missing command exits 127.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
* Refuse a git URL naming a transport Omarchy does not clone from
`<helper>::<address>` is only one of the two ways a URL reaches a remote helper. git also resolves git-remote-<scheme> for `<scheme>://<address>` whenever the scheme is not one it connects itself, so `ext::sh -c id` and `ext://sh -c id` arrive at the same helper while only the first was refused.
That shape cannot be refused outright, because it is also how every legitimate URL arrives, so the scheme is checked against the transports git still connects itself. `git+ssh` and `ssh+git` are on that list: they are spelled like a helper and read as plain ssh, and leaving them off would refuse a URL that clones today. `ext` and `fd` are off it deliberately -- git ships a helper for each, and `ext` runs whatever command the URL carries.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
---------
Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Codex XHigh <noreply@openai.com>
* Guard plugin-add against git transport-helper URLs
omarchy-plugin-add cloned a user-supplied git URL without the
transport-helper guard that omarchy-theme-install already applies
(added in #7884, which did not touch plugin-add). Port that guard
(reject ext::/fd:: and leading-dash forms, keep https/ssh/scp-style
incl. IPv6) and add a regression test. Stock systems are unaffected
(git default protocol.ext.allow=never); this removes the silent
dependency on that default and aligns the two install paths.
* Test the plugin-add guard's leading-dash arm via the gum input path
The prior leading-dash cases only exercised the argv option parser, not
the guard (removing the guard's -* arm left them green). Drive a dash
value through the interactive gum prompt under a pty so the post-input
guard is actually covered; skip cleanly where util-linux script is
unavailable.
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>
Let users add trusted plugin source repos and add/update/remove plugins
from them, alongside the existing in-shell plugin commands:
omarchy plugin source <add|list|remove|refresh>
omarchy plugin available
omarchy plugin add | update | remove | validate
Each command is interactive (gum/fzf pickers, confirmation, an update
diff) in a TTY and fully flag-driven with --yes for scripts and agents.
Sources live in ~/.config/omarchy/plugins/sources.json and clone into
~/.cache/omarchy/plugin-sources/. The installer only copies files,
validates manifests against the shell's schema, and toggles enabled
state over IPC -- it never runs plugin code, hooks, or sudo.
Also guard 'plugin bar add' so only known bar-widget ids enter the
layout (rejecting typos/non-widgets like a stray '--help').