feat(plugin): install plugins from a GitHub repository URL - #221
Conversation
Allow `/plugins install <github-url>` (and marketplace `source` entries) to take a GitHub repo URL directly. A new `github` source kind joins the existing `local-path` and `zip-url` kinds. Recognized URL forms (parsing in source.ts): - `https://github.com/<o>/<r>` — bare; resolves to latest release tag, falling back to default branch HEAD. - `https://github.com/<o>/<r>/tree/<ref>` — branch / tag / SHA; value passed to codeload in its short form so the backend resolves either. - `https://github.com/<o>/<r>/releases/tag/<tag>` — explicit tag, uses refs/tags/<tag> to avoid same-named-branch ambiguity. - `https://github.com/<o>/<r>/commit/<sha>` — explicit commit SHA. The resolver deliberately avoids `api.github.com`: its 60/hour anonymous quota is shared with every other tool on the egress IP (browser, gh CLI, IDE integrations) and a first-time install failing because some other tool ate the budget is unacceptable UX. Instead we: - GET `github.com/<o>/<r>/releases/latest` with manual redirect and parse the `Location` header (302 → tag URL; 404 → no own release). - Fall back to `codeload.github.com/<o>/<r>/zip/HEAD` for repos with no releases (or for forks that inherit upstream tags but have no own release page, which redirect to bare `/releases`). - Only treat the explicit 404 from `/releases/latest` as "no release" — 5xx, 403, 429, and any other non-2xx status surface a hard error rather than silently installing the default branch, so the user knows when transient GitHub issues changed the install path. UI changes in the TUI: - `/plugins install` now shows a live Braille spinner while resolving and downloading, then flips to a final status that distinguishes Installed (fresh) vs Updated (same repo identity, new version) vs Migrated (source changed, e.g. CDN zip-url → GitHub). - `/plugins list`, the `/plugins` overview, and `/plugins info` show the install provenance inline. `zip-url` installs now display the URL host (e.g. `via code.kimi.com`, `via 127.0.0.1:port`) instead of the opaque `zip-url` literal. GitHub installs show `github <owner>/<repo>@<ref>`. - Three-tier trust badge driven by the marketplace context recorded at install time: `official` (green) for `tier: official`, `curated` (blue) for `tier: curated`, `third-party` (muted) for anything not installed through the marketplace selector. CLI `/plugins install <url>` always records as third-party; the marketplace selector passes the tier through. A re-install replaces the marketplace context: switching to a third-party source clears the badge, which matches the underlying trust change. `installed.json` gains optional `github` and `marketplace` fields (back-compatible). PluginSummary surfaces `source`, `originalSource`, `github`, and `marketplace` so the TUI can label installs without an extra round trip to PluginInfo. The SDK's `session.installPlugin(source)` gains an optional `{ marketplace }` second argument so the marketplace selector can forward `{ id, tier }` through RPC; the CLI install path omits it. Tests: 112 plugin-suite tests (URL parser, resolver, store round-trip, manager integration). The manager integration tests assert codeload URLs shape (short form for `/tree/<ref>`, explicit `refs/tags/` for `/releases/tag/`) and verify marketplace context is persisted across reloads and cleared on a third-party re-install.
🦋 Changeset detectedLatest commit: b1da10d The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
commit: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 362af98985
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| // marketplace context is replaced on each install — a re-install from a | ||
| // non-marketplace source MUST clear any prior `kimi-official` badge, | ||
| // and a marketplace re-install MUST attach one regardless of prior state. | ||
| marketplace: options?.marketplace, |
There was a problem hiding this comment.
Validate marketplace trust before persisting badges
When install() is called through the public SDK/RPC path, the caller can pass any marketplace object and this line persists it without checking that the installed source actually came from the trusted marketplace or that the marketplace id matches the manifest. In that scenario a third-party local/GitHub plugin can set { tier: 'official' }, and the TUI's trust label will render it as Kimi-official, undermining the provenance badge. Keep this metadata internal to the marketplace flow or verify it against a trusted marketplace entry before storing it.
Useful? React with 👍 / 👎.
| const match = /\/releases\/tag\/([^/?#]+)/.exec(location); | ||
| if (match === null) return undefined; | ||
| try { | ||
| return decodeURIComponent(match[1]!); |
There was a problem hiding this comment.
Preserve encoded tag characters for codeload URLs
For bare GitHub installs whose latest release tag contains a URL-reserved but Git-valid character such as #, GitHub redirects with the tag encoded (for example %23), but decoding it here stores the raw #. That value is then interpolated into the codeload URL, where #... becomes a fragment instead of part of the tag path, so the download targets the wrong/truncated ref and the install fails even though the release URL was valid.
Useful? React with 👍 / 👎.
Git permits ref characters that have special meaning in URLs — most notably `#`, which is a valid tag character (e.g. `release#1`) but the URL fragment delimiter. The resolver decoded the tag from GitHub's `/releases/latest` 302 redirect Location header and then interpolated the raw value into the codeload URL. The literal `#1` became a fragment and the HTTP request reached the server as `…/refs/tags/release` — a wrong or truncated ref, leading to install failure for a release whose URL was otherwise valid. Two symmetric changes: - The codeload URL builder now splits the ref on `/` (so multi-segment refs like `feat/foo` keep their path separators) and percent-encodes each segment. - The GitHub URL parser now percent-decodes each segment from the URL's pathname when extracting `/tree/<ref>`, `/releases/tag/<tag>`, and `/commit/<sha>`. Storage and display see the human-readable Git ref name; the resolver re-encodes on the way out. Malformed `%xx` sequences in user-typed URLs are tolerated: we keep the raw segment so the caller surfaces a normal "ref not found" error downstream instead of crashing during parse.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ad7c74b149
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
|
||
| GitHub URLs accept four forms. The bare URL `https://github.com/<owner>/<repo>` installs the repository's latest GitHub release; if the repo has no release, the default branch is installed instead. `https://github.com/<owner>/<repo>/tree/<ref>` installs a specific branch, tag, or short commit SHA. `https://github.com/<owner>/<repo>/releases/tag/<tag>` and `https://github.com/<owner>/<repo>/commit/<sha>` pin to an explicit tag or commit. Network calls go to `github.com` redirects and `codeload.github.com` archive downloads only; `api.github.com` is not used. | ||
|
|
||
| The plugin manager shows each install's source and a trust badge. `kimi-official` marks plugins whose source matches an `official` entry from the built-in Kimi marketplace and is downloaded from `https://code.kimi.com`; `curated` uses the same check for built-in marketplace entries at the `curated` tier. `third-party` marks anything else, including plugins installed directly with `/plugins install <url>`, from a local directory, or from a custom marketplace source. If the source cannot be verified against the built-in marketplace, Kimi Code CLI does not show a trust badge for it. |
There was a problem hiding this comment.
Fix contradictory trust-badge wording
For unverifiable sources such as direct GitHub, local, or custom-marketplace installs, the updated UI still renders the third-party trust label/badge, and this same paragraph already says third-party marks those cases. The final sentence says no trust badge is shown, which will mislead users reading the plugin docs; please reword it to say no official/curated badge is shown, or that it falls back to third-party.
Useful? React with 👍 / 👎.
| const marketplace = await loadPluginMarketplace({ | ||
| workDir: host.state.appState.workDir, | ||
| source: KIMI_CODE_PLUGIN_MARKETPLACE_URL, | ||
| }); |
There was a problem hiding this comment.
Avoid blocking plugin views on CDN trust lookup
When any installed plugin has a code.kimi.com source, opening /plugins, /plugins list, or /plugins info now awaits this built-in marketplace fetch only to decorate trust badges. loadPluginMarketplace performs a remote fetch with no timeout, so users behind a blocked or hanging CDN can have the whole plugin manager stall even though the trust context is optional and failures are swallowed; please add a short timeout/cache or avoid doing this lookup on the render path.
Useful? React with 👍 / 👎.
| const url = `https://github.com/${owner}/${repo}/releases/latest`; | ||
| const resp = await fetch(url, { redirect: 'manual' }); |
There was a problem hiding this comment.
Add timeouts to bare GitHub resolution requests
For bare GitHub installs, this fetch and the later codeload HEAD probe run without an AbortSignal, so a blocked or half-open GitHub connection can leave /plugins install https://github.com/<owner>/<repo> spinning indefinitely before it ever reaches downloadZip's timeout. Please apply a bounded signal here and to the HEAD probe so failed network paths surface as install errors.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b1da10d72b
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| readonly source: PluginSource; | ||
| readonly originalSource?: string; | ||
| readonly github?: PluginGithubMetadata; |
There was a problem hiding this comment.
Keep new summary metadata source-compatible
Because PluginSummary is re-exported by @moonshot-ai/kimi-code-sdk, making source, originalSource, and github required on the summary shape is a type-level breaking change for downstream callers that construct summaries in tests, wrappers, or custom RPC fixtures; those object literals that compiled against the previous SDK now fail after a minor update. Please make the new metadata optional on PluginSummary (or treat this as a major API change) while still populating it from the built-in manager.
Useful? React with 👍 / 👎.
Related Issue
No prior issue. The feature was scoped and implemented in a maintainer session - the design notes live in
docs/superpowers/specs/2026-05-29-plugin-install-from-github-design.md(gitignored, locally maintained).Problem
/plugins installpreviously accepted only a local directory or a zip URL. Sharing a plugin meant publishing a zip somewhere (or putting it in the Kimi marketplace). Anyone wanting to share a workflow plugin from GitHub had no first-class path - you would have to copy the/archive/...zipURL from the GitHub UI, which is brittle and unintuitive.The plugin manager also needs clear provenance labels without letting public SDK/RPC callers forge a Kimi trust badge. Marketplace metadata cannot be accepted from arbitrary callers because a third-party install could otherwise claim
tier: official.What changed
/plugins installnow accepts GitHub repository URLs directly. Four URL forms are recognized:https://github.com/<o>/<r>- bare, resolves to the latest GitHub release (falls back to default branch if the repo has no release).https://github.com/<o>/<r>/tree/<ref>- branch / tag / short SHA.https://github.com/<o>/<r>/releases/tag/<tag>- explicit release tag.https://github.com/<o>/<r>/commit/<sha>- explicit commit SHA.The resolver deliberately bypasses
api.github.com. Its 60/hour anonymous quota is shared with every other GitHub-touching tool on the egress IP (browsers, gh CLI, IDE integrations), so a first-time install failing because some other tool burned the budget is unacceptable UX. We usegithub.com/<o>/<r>/releases/latestredirects andcodeload.github.com/<o>/<r>/zip/HEADinstead. Transient errors fromgithub.com(5xx, 403, 429, anything other than 301/302/404) are surfaced as a hard error rather than silently falling back to the default branch - silently swapping content the user did not ask for is worse than failing loudly.The plugin manager now displays each install's provenance and trust level:
zip-urlliteral with something useful:via <host>for zip URL installs (e.g.via code.kimi.com),github <owner>/<repo>@<ref>for GitHub installs,local-pathfor local installs.kimi-officialfor entries that match the built-in Kimi marketplace attier: officialand whose artifact is downloaded fromhttps://code.kimi.com,curatedfor the same check attier: curated, andthird-partyfor everything else.installPlugin(source). It no longer accepts or persists marketplace badge metadata, so local, GitHub, direct URL, custom marketplace, and dev-loopback installs cannot forge Kimi trust./plugins installalso gains a live Braille spinner while resolving and downloading (reusing the existingMoonLoaderused by OAuth login). The post-install status line distinguishes Installed (fresh), Updated (same repo identity, new version), and Migrated (source identity changed, e.g. CDN -> GitHub).Checklist
gen-changesetsskill, or this PR needs no changeset.gen-docsskill, or this PR needs no doc update.