| 1 | # Installing plugins |
| 2 | |
| 3 | To create a bundle, start with [Write your first Codewhale plugin](PLUGIN_AUTHORING.md) |
| 4 | and its runnable Skills example. |
| 5 | |
| 6 | This is the walkthrough for the `/plugin install` on-ramp (v0.9.4, #5182). |
| 7 | [PLUGIN_BUNDLES.md](PLUGIN_BUNDLES.md) remains the contract for the bundle |
| 8 | format (`plugin.json`, compatible `kimi.plugin.json` or |
| 9 | `.claude-plugin/plugin.json`, or legacy `plugin.toml`), discovery, validation, and |
| 10 | the trust/enable lifecycle — this document covers how bits get onto disk in |
| 11 | the first place. |
| 12 | |
| 13 | `/plugin suggest <task>` is a local, read-only companion: it ranks already |
| 14 | installed bundles (name, keywords, description, bundled skill names, declared |
| 15 | hosts) and any marketplace catalogs you have added with `/plugin marketplace add`. |
| 16 | It explains the match and gives the next review, enable, or catalog-install |
| 17 | step, but never installs, trusts, or enables a bundle on its own. |
| 18 | |
| 19 | Sending a task also surfaces one quiet toast when the prompt strongly matches |
| 20 | an installed-but-idle plugin or a catalog candidate you do not have yet — for |
| 21 | example a prompt about Supabase suggesting `/plugin trust supabase` or |
| 22 | `/plugin marketplace install <catalog> supabase`. Description-only matches do |
| 23 | not toast. While you type, a one-line composer CTA (`Install {name} plugin?`) |
| 24 | offers the same review command after a short debounce; it never auto-installs, |
| 25 | hides when the plugin is already active, and stays dismissed for that name |
| 26 | this session. Matching idle or catalog plugins are also appended on send as |
| 27 | an `<recommended_plugins>` user-turn block (not the pinned system prefix); |
| 28 | the model can call `request_plugin_install` to surface review for the human |
| 29 | without changing disk. Codewhale does not invent a remote plugin URL; missing |
| 30 | plugins are suggested only from catalogs you added. On-disk bundle changes |
| 31 | still toast `/plugin reload` on send and between turns. |
| 32 | |
| 33 | ## Sources |
| 34 | |
| 35 | `/plugin install <spec>` accepts three source kinds: |
| 36 | |
| 37 | ```text |
| 38 | /plugin install ./path/to/bundle # local directory (copied) |
| 39 | /plugin install github:owner/repo # GitHub archive of the default branch |
| 40 | /plugin install https://example.com/x.tar.gz # direct tarball URL |
| 41 | ``` |
| 42 | |
| 43 | There is no registry index and no `git clone` in v1 — tarball-only fetching |
| 44 | keeps the size cap and no-symlink guarantees of the installer. Downloads are |
| 45 | gated by the per-domain network policy: an unknown host returns a |
| 46 | "needs approval" error naming the host (`/network allow <host>`, then retry), |
| 47 | a denied host aborts without touching disk. |
| 48 | |
| 49 | The fetched tree must contain **exactly one** bundle root — a directory |
| 50 | holding a `plugin.json`, compatible `kimi.plugin.json`, |
| 51 | `.claude-plugin/plugin.json`, or legacy `plugin.toml` manifest. Kimi bundles are accepted when they use Codewhale-compatible Skills, |
| 52 | commands, agents, and MCP declarations; unsupported Kimi runtime fields fail |
| 53 | closed instead of being silently ignored. Bundles land in |
| 54 | the user plugins root at `~/.codewhale/plugins/<name>/`, where `<name>` is the |
| 55 | manifest's plugin name. |
| 56 | |
| 57 | Claude bundles keep their metadata in `.claude-plugin/plugin.json` and their |
| 58 | components at the bundle root. The importer supports skills, commands, agents, |
| 59 | and MCP servers declared inline or in root `.mcp.json` (flat server map or an |
| 60 | `mcpServers` wrapper). Claude `http` transport maps to Streamable HTTP. Relative |
| 61 | sources in a `.claude-plugin/marketplace.json` catalog resolve from the marketplace |
| 62 | repository root. The whole bundle remains subject to the same review hashes and |
| 63 | path checks as native plugins. |
| 64 | |
| 65 | Remote MCP headers can name credentials without embedding them: exact |
| 66 | `Bearer ${ENV_NAME}` authorization values become `bearer_token_env_var`, and |
| 67 | exact `${ENV_NAME}` header values become `env_headers`. Import reads no credential |
| 68 | values. Literal credentials and compound templates are rejected. |
| 69 | |
| 70 | This is a compatible subset: hooks, LSP declarations, custom MCP file paths, and |
| 71 | `${CLAUDE_PLUGIN_ROOT}` expansion are rejected with an explanation; no partial |
| 72 | plugin is installed. Installing a remote MCP declaration does not complete its |
| 73 | authentication. Plugin-contributed remote servers retain the existing explicit |
| 74 | credential requirements; this importer does not enable plugin OAuth. |
| 75 | |
| 76 | ## The guided flow |
| 77 | |
| 78 | Installing never activates anything. The command places the bits, then drops |
| 79 | you straight into the standard capability review: |
| 80 | |
| 81 | ```text |
| 82 | /plugin install github:someone/neat-plugin |
| 83 | → Installed plugin 'neat-plugin' to ~/.codewhale/plugins/neat-plugin. |
| 84 | It is disabled and untrusted. Review its requested authority below… |
| 85 | <full inventory, permissions, MCP authority render> |
| 86 | /plugin trust neat-plugin <content-hash>.<capability-hash> |
| 87 | |
| 88 | /plugin trust neat-plugin <paste the token> # records the hash-bound receipt |
| 89 | /plugin enable neat-plugin # activates for this workspace |
| 90 | ``` |
| 91 | |
| 92 | This is the same review render and confirmation token as `/plugin trust |
| 93 | <name>` — trust is the strict hash-bound receipt flow, not an advisory marker. |
| 94 | If the bundle's content or declared capabilities change, the receipt stops |
| 95 | matching and the plugin goes inactive until you review again. |
| 96 | |
| 97 | ## Update and uninstall |
| 98 | |
| 99 | ```text |
| 100 | /plugin update <name> # re-download, byte-compare, atomic swap if changed |
| 101 | /plugin disable <name> # required before uninstall |
| 102 | /plugin uninstall <name> # deletes the bundle and prunes its state entry |
| 103 | ``` |
| 104 | |
| 105 | - `update` re-downloads the recorded source. Identical bytes are a no-op; a |
| 106 | changed bundle is swapped atomically and its trust receipt is automatically |
| 107 | invalidated (the hash no longer matches), so re-review is forced before the |
| 108 | plugin can activate again. Plugins installed from a local path cannot be |
| 109 | re-downloaded. To replace their installed copy, disable and uninstall it, |
| 110 | then run `/plugin install <path>` and review the new bundle; the original |
| 111 | source directory is left intact. See the [local authoring loop](PLUGIN_AUTHORING.md#4-iterate-and-review-changes). |
| 112 | - `uninstall` refuses enabled plugins (disable first), deletes the bundle |
| 113 | directory, and removes its persisted trust/enablement entry. |
| 114 | |
| 115 | ## Safety rules |
| 116 | |
| 117 | - Every install carries an `.installed-from` provenance marker. The installer |
| 118 | **refuses to overwrite or delete** a bundle that lacks it — hand-placed |
| 119 | bundles under `~/.codewhale/plugins/` are never clobbered. |
| 120 | - Tarballs are size-capped and extracted into a private staging directory |
| 121 | first; path traversal (`..`, absolute paths) and symlinks/hard links inside |
| 122 | the bundle are rejected, and the destination only appears via an atomic |
| 123 | rename after every check passes. |
| 124 | - Install pre-checks the name against builtin and workspace bundles so a |
| 125 | higher-precedence bundle cannot silently shadow (or be shadowed by) the |
| 126 | install. |
| 127 | - Newly installed bits are always **disabled and untrusted**; enablement only |
| 128 | ever happens through the explicit trust review above. |
| 129 |