Skip to content

feat(plugin): install plugins from a GitHub repository URL - #221

Merged
wbxl2000 merged 7 commits into
mainfrom
qer/feat-plugin-install-from-github
May 29, 2026
Merged

feat(plugin): install plugins from a GitHub repository URL#221
wbxl2000 merged 7 commits into
mainfrom
qer/feat-plugin-install-from-github

Conversation

@wbxl2000

@wbxl2000 wbxl2000 commented May 29, 2026

Copy link
Copy Markdown
Collaborator

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 install previously 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/...zip URL 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 install now 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 use github.com/<o>/<r>/releases/latest redirects and codeload.github.com/<o>/<r>/zip/HEAD instead. Transient errors from github.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:

  • A source label that replaces the previous opaque zip-url literal with something useful: via <host> for zip URL installs (e.g. via code.kimi.com), github <owner>/<repo>@<ref> for GitHub installs, local-path for local installs.
  • A three-tier trust badge: kimi-official for entries that match the built-in Kimi marketplace at tier: official and whose artifact is downloaded from https://code.kimi.com, curated for the same check at tier: curated, and third-party for everything else.
  • The public SDK/RPC install path is plain 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 install also gains a live Braille spinner while resolving and downloading (reusing the existing MoonLoader used 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

  • I have read the CONTRIBUTING document.
  • I have linked a related issue, or explained the problem above.
  • I have added tests that prove my feature works.
  • Ran gen-changesets skill, or this PR needs no changeset.
  • Ran gen-docs skill, or this PR needs no doc update.

qer added 3 commits May 29, 2026 21:00
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-bot

changeset-bot Bot commented May 29, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b1da10d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@moonshot-ai/agent-core Minor
@moonshot-ai/kimi-code-sdk Minor
@moonshot-ai/kimi-code Minor
@moonshot-ai/migration-legacy Patch

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

@pkg-pr-new

pkg-pr-new Bot commented May 29, 2026

Copy link
Copy Markdown
pnpm dlx https://pkg.pr.new/@moonshot-ai/kimi-code@b1da10d
npx https://pkg.pr.new/@moonshot-ai/kimi-code@b1da10d

commit: b1da10d

@wbxl2000

Copy link
Copy Markdown
Collaborator Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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]!);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

qer and others added 2 commits May 29, 2026 21:48
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.
@wbxl2000

Copy link
Copy Markdown
Collaborator Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread docs/en/customization/plugins.md Outdated

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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 Badge 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 👍 / 👎.

Comment on lines +217 to +220
const marketplace = await loadPluginMarketplace({
workDir: host.state.appState.workDir,
source: KIMI_CODE_PLUGIN_MARKETPLACE_URL,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Comment on lines +96 to +97
const url = `https://github.com/${owner}/${repo}/releases/latest`;
const resp = await fetch(url, { redirect: 'manual' });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@wbxl2000
wbxl2000 merged commit bab2da7 into main May 29, 2026
6 checks passed
@wbxl2000
wbxl2000 deleted the qer/feat-plugin-install-from-github branch May 29, 2026 14:18

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +109 to +111
readonly source: PluginSource;
readonly originalSource?: string;
readonly github?: PluginGithubMetadata;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@github-actions github-actions Bot mentioned this pull request May 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant