feat: support multi-plugin repos with subpath in git URLs#1529
feat: support multi-plugin repos with subpath in git URLs#1529
Conversation
Add _parse_git_url() to split git URLs into (clone_url, subpath),
supporting .git URLs, SSH, GitHub/GitLab short URLs, and browser
tree/{branch}/ URLs. When no subpath is given and root has no
plugin.json, scan subdirectories and suggest available plugins.
There was a problem hiding this comment.
Pull request overview
Adds support to kimi plugin install for repositories that contain multiple plugins in subdirectories by parsing git URLs into a clone URL plus an optional subpath, then resolving/validating that subpath after cloning and suggesting available plugins when plugin.json is not at repo root.
Changes:
- Introduces
_parse_git_url()to split git URLs into(clone_url, subpath)and handle common GitHub/GitLab URL patterns. - Updates
_resolve_source()to cloneclone_url, then install from a validatedsubpathor scan/suggest sub-plugins when the repo root isn’t a plugin. - Adds unit + integration-style tests covering URL parsing and subpath resolution behavior (including traversal rejection).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
src/kimi_cli/cli/plugin.py |
Adds git URL parsing and extends git clone resolution to support subpaths and plugin discovery/suggestions. |
tests/core/test_plugin.py |
Adds parametrized tests for _parse_git_url() and mocked-clone tests for _resolve_source() subpath behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/kimi_cli/cli/plugin.py
Outdated
| # Strip tree/{branch}/ prefix (single-segment branch only) | ||
| if len(rest_segments) >= 2 and rest_segments[0] == "tree": | ||
| rest_segments = rest_segments[2:] | ||
|
|
||
| subpath = "/".join(rest_segments) or None | ||
| return clone_url, subpath |
There was a problem hiding this comment.
_parse_git_url() strips tree/{branch}/ but drops the branch entirely, and _resolve_source() always clones the default branch. This will install the wrong content for URLs that specify a non-default branch (e.g., /tree/develop/...). Consider parsing and returning the ref/branch and passing it to git clone (e.g., --branch/--single-branch), or explicitly rejecting non-default-branch browser URLs with a clear error.
|
|
||
| # Scan one level for available plugins | ||
| available = sorted( | ||
| d.name for d in repo_root.iterdir() if d.is_dir() and (d / "plugin.json").exists() |
There was a problem hiding this comment.
The available = sorted(...) comprehension is on a single line and appears to exceed the repo’s Ruff line-length = 100 limit (E501 is not ignored for src/**). This will likely fail lint/typecheck CI; please wrap the generator and conditions across multiple lines.
| d.name for d in repo_root.iterdir() if d.is_dir() and (d / "plugin.json").exists() | |
| d.name | |
| for d in repo_root.iterdir() | |
| if d.is_dir() and (d / "plugin.json").exists() |
| "my-plugin", | ||
| ), | ||
| ( | ||
| "https://gitlab.com/org/repo/tree/main/my-plugin", |
There was a problem hiding this comment.
These GitLab “browser URL” test cases use /tree/<branch>/..., but GitLab’s canonical web URLs typically include /-/tree/<branch>/.... As written, the tests may give a false sense of GitLab support. Either adjust the parser (and tests) to handle /-/tree/ or constrain the docs/tests to GitHub-style URLs only.
| "https://gitlab.com/org/repo/tree/main/my-plugin", | |
| "https://gitlab.com/org/repo/-/tree/main/my-plugin", |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 61c36f0f8b
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if len(rest_segments) >= 2 and rest_segments[0] == "tree": | ||
| rest_segments = rest_segments[2:] |
There was a problem hiding this comment.
Keep the selected branch from browser
tree/... URLs
When a user pastes a browser URL such as https://github.com/org/repo/tree/release-1.2/my-plugin, this code removes tree/release-1.2 but never carries the branch into _resolve_source(), which still runs plain git clone --depth 1 <repo>. I checked git clone -h: --branch is what makes clone checkout <branch> instead of the remote's HEAD. For any non-default branch, install will either report subpath not found or silently install the copy from the default branch if the same path exists there.
Useful? React with 👍 / 👎.
| owner_repo = "/".join(segments[:2]) | ||
| clone_url = f"{parsed.scheme}://{parsed.netloc}/{owner_repo}" |
There was a problem hiding this comment.
Preserve GitLab subgroup namespaces in short URLs
GitLab short URLs are not always owner/repo: projects under subgroups have paths like group/subgroup/repo. Slicing to segments[:2] turns https://gitlab.com/group/subgroup/repo/my-plugin into a clone URL of https://gitlab.com/group/subgroup and a subpath of repo/my-plugin, so subgroup-hosted plugin repos cannot be cloned or resolved at all even though this change advertises GitLab short URL support.
Useful? React with 👍 / 👎.
- _parse_git_url now returns (clone_url, subpath, branch) triple
- Pass --branch to git clone when branch is extracted from tree/ URLs
- Handle GitLab's /-/tree/{branch}/ URL format
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 92014516cd
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if len(rest_segments) >= 2 and rest_segments[0] == "tree": | ||
| branch = rest_segments[1] | ||
| rest_segments = rest_segments[2:] |
There was a problem hiding this comment.
Handle slash-containing tree refs correctly
When a pasted browser URL points at a ref containing / (for example feature/foo or GitHub’s dependabot/... branches), _parse_git_url() only captures rest_segments[1] as the branch and treats the remaining ref segments as part of the plugin path. _resolve_source() then runs git clone --branch <first-segment> and looks for the plugin under the wrong directory, so valid .../tree/<ref>/<plugin> URLs either fail to clone or resolve the wrong source tree.
Useful? React with 👍 / 👎.
Add comprehensive documentation for the plugin system: - Add changelog entries for plugin system, multi-plugin repos, and credential injection with OAuth/env var support - Create new plugins.md page (zh/en) covering installation, creation, credential injection, and tool script specifications - Update skills.md to add comparison between Skills and Plugins - Update vitepress config to include Plugins in navigation
Summary
_parse_git_url()to parse git URLs into(clone_url, subpath), supporting.gitURLs, SSH, GitHub/GitLab short URLs, and browsertree/{branch}/URLsplugin.json, scan subdirectories and suggest available plugins with the correct install URLis_relative_to()on resolved subpathSupported URL formats
.git+ subpathhttps://github.com/org/repo.git/my-plugingit@github.com:org/repo.git/my-pluginhttps://github.com/org/repo/my-pluginhttps://github.com/org/repo/tree/main/my-pluginScan-and-suggest example
Test plan
_parse_git_url(all URL formats + edge cases)_resolve_sourcegit subpath (with mockedgit clone)MoonshotAI/kimi-cli-plugins)