| 1 | # Installing plugins |
| 2 | |
| 3 | This is the walkthrough for the `/plugin install` on-ramp (v0.9.4, #5182). |
| 4 | [PLUGIN_BUNDLES.md](PLUGIN_BUNDLES.md) remains the contract for the bundle |
| 5 | format, discovery, and the trust/enable lifecycle — this document covers how |
| 6 | bits get onto disk in the first place. |
| 7 | |
| 8 | `/plugin suggest <task>` is a local, read-only companion: it ranks already |
| 9 | installed bundles by their validated name, description, bundled skill names, |
| 10 | and declared hosts. It explains the match and gives the next review/enable |
| 11 | step, but never installs, trusts, or enables a bundle. Codewhale deliberately |
| 12 | does not treat arbitrary remote archives as a plugin marketplace; a remote |
| 13 | catalog needs publisher and provenance policy before it can make suggestions. |
| 14 | |
| 15 | ## Sources |
| 16 | |
| 17 | `/plugin install <spec>` accepts three source kinds: |
| 18 | |
| 19 | ```text |
| 20 | /plugin install ./path/to/bundle # local directory (copied) |
| 21 | /plugin install github:owner/repo # GitHub archive of the default branch |
| 22 | /plugin install https://example.com/x.tar.gz # direct tarball URL |
| 23 | ``` |
| 24 | |
| 25 | There is no registry index and no `git clone` in v1 — tarball-only fetching |
| 26 | keeps the size cap and no-symlink guarantees of the installer. Downloads are |
| 27 | gated by the per-domain network policy: an unknown host returns a |
| 28 | "needs approval" error naming the host (`/network allow <host>`, then retry), |
| 29 | a denied host aborts without touching disk. |
| 30 | |
| 31 | The fetched tree must contain **exactly one** `plugin.toml`; that file's |
| 32 | directory becomes the bundle root. Bundles land in the user plugins root at |
| 33 | `~/.codewhale/plugins/<name>/`, where `<name>` is the manifest `[plugin].name`. |
| 34 | |
| 35 | ## The guided flow |
| 36 | |
| 37 | Installing never activates anything. The command places the bits, then drops |
| 38 | you straight into the standard capability review: |
| 39 | |
| 40 | ```text |
| 41 | /plugin install github:someone/neat-plugin |
| 42 | → Installed plugin 'neat-plugin' to ~/.codewhale/plugins/neat-plugin. |
| 43 | It is disabled and untrusted. Review its requested authority below… |
| 44 | <full inventory, permissions, MCP authority render> |
| 45 | /plugin trust neat-plugin <content-hash>.<capability-hash> |
| 46 | |
| 47 | /plugin trust neat-plugin <paste the token> # records the hash-bound receipt |
| 48 | /plugin enable neat-plugin # activates for this workspace |
| 49 | ``` |
| 50 | |
| 51 | This is the same review render and confirmation token as `/plugin trust |
| 52 | <name>` — trust is the strict hash-bound receipt flow, not an advisory marker. |
| 53 | If the bundle's content or declared capabilities change, the receipt stops |
| 54 | matching and the plugin goes inactive until you review again. |
| 55 | |
| 56 | ## Update and uninstall |
| 57 | |
| 58 | ```text |
| 59 | /plugin update <name> # re-download, byte-compare, atomic swap if changed |
| 60 | /plugin disable <name> # required before uninstall |
| 61 | /plugin uninstall <name> # deletes the bundle and prunes its state entry |
| 62 | ``` |
| 63 | |
| 64 | - `update` re-downloads the recorded source. Identical bytes are a no-op; a |
| 65 | changed bundle is swapped atomically and its trust receipt is automatically |
| 66 | invalidated (the hash no longer matches), so re-review is forced before the |
| 67 | plugin can activate again. Plugins installed from a local path cannot be |
| 68 | re-downloaded — reinstall them with `/plugin install <path>`. |
| 69 | - `uninstall` refuses enabled plugins (disable first), deletes the bundle |
| 70 | directory, and removes its persisted trust/enablement entry. |
| 71 | |
| 72 | ## Safety rules |
| 73 | |
| 74 | - Every install carries an `.installed-from` provenance marker. The installer |
| 75 | **refuses to overwrite or delete** a bundle that lacks it — hand-placed |
| 76 | bundles under `~/.codewhale/plugins/` are never clobbered. |
| 77 | - Tarballs are size-capped and extracted into a private staging directory |
| 78 | first; path traversal (`..`, absolute paths) and symlinks/hard links inside |
| 79 | the bundle are rejected, and the destination only appears via an atomic |
| 80 | rename after every check passes. |
| 81 | - Install pre-checks the name against builtin and workspace bundles so a |
| 82 | higher-precedence bundle cannot silently shadow (or be shadowed by) the |
| 83 | install. |
| 84 | - Newly installed bits are always **disabled and untrusted**; enablement only |
| 85 | ever happens through the explicit trust review above. |
| 86 |