feat: add CNB & Gitea platform support, Chinese README#168
Conversation
Support CNB (Cloud Native Base, cnb.cool) as a git hosting platform for installing skills. This adds platform detection, token authentication via CNB_TOKEN, host configuration (cnb_hosts / SKILLSHARE_CNB_HOSTS), and URL parsing for both direct and web URLs (including /-/tree paths). Changes: - auth.go: add PlatformCNB, detect cnb.cool hosts, resolve CNB_TOKEN - config.go / project.go: add cnb_hosts config field with normalization and env var merge, plus EffectiveCNBHosts() accessor - source.go: add isCNBHost() and CNBHosts to ParseOptions - install.go: pass CNBHosts from config to source parsing - install_git.go: include CNB_TOKEN in auth error hints
Support Gitea (gitea.com and self-hosted instances) as a git hosting
platform for installing skills. Includes both core platform integration
and a Gitea Contents API fast path for direct file downloads without
full git clone.
Platform support (P0):
- auth.go: add PlatformGitea, detect gitea hosts, resolve GITEA_TOKEN
- config.go / project.go: add gitea_hosts config field with normalization,
env var merge (SKILLSHARE_GITEA_HOSTS), and EffectiveGiteaHosts()
- source.go: add isGiteaHost() and GiteaHosts to ParseOptions
- install.go: pass GiteaHosts from config to source parsing
- install_git.go: include GITEA_TOKEN in auth error hints
API fast path (P1):
- gitea_download.go: Gitea Contents API recursive directory download
(GET /api/v1/repos/{owner}/{repo}/contents/{path}), matching the
GitHub Contents API pattern
- install_apply.go / install_discovery.go: hook Gitea API fallback
after sparse checkout, before full clone
There was a problem hiding this comment.
Code Review
This pull request introduces support for CNB and Gitea platforms, allowing users to configure custom hosts and authenticate using platform-specific tokens (CNB_TOKEN and GITEA_TOKEN). It also implements a fast-path download and discovery mechanism for Gitea repositories using the Gitea Contents API. However, several critical issues were identified in the new Gitea implementation: a compilation error due to an undefined function shortCommitHash, incorrect URL path escaping that breaks subdirectory downloads, and fragile SSH URL parsing. Additionally, warnings are discarded in the Gitea discovery fast path, and the newly added helper functions isCNBHost and isGiteaHost are currently unused.
| if err != nil { | ||
| return "", nil | ||
| } | ||
| return shortCommitHash(commitHash), nil |
| contentsURL := fmt.Sprintf("%s/repos/%s/%s/contents/%s", | ||
| strings.TrimRight(apiBase, "/"), owner, repo, url.PathEscape(path)) |
There was a problem hiding this comment.
Using url.PathEscape(path) on the entire path will escape the / characters to %2F. This will break Gitea Contents API requests for subdirectories (e.g., skills/pdf becomes skills%2Fpdf, leading to 404 errors). Instead, you should escape each path segment individually and join them back with /.
var escapedSegments []string
for _, seg := range strings.Split(path, "/") {
escapedSegments = append(escapedSegments, url.PathEscape(seg))
}
contentsURL := fmt.Sprintf("%s/repos/%s/%s/contents/%s",
strings.TrimRight(apiBase, "/"), owner, repo, strings.Join(escapedSegments, "/"))| // SSH: git@host:owner/repo | ||
| if strings.HasPrefix(u, "git@") { | ||
| parts := strings.Split(u, ":") | ||
| if len(parts) == 2 { | ||
| segments := strings.Split(parts[1], "/") | ||
| if len(segments) >= 2 { | ||
| return segments[0], segments[1] | ||
| } | ||
| } | ||
| return "", "" | ||
| } |
There was a problem hiding this comment.
Using strings.Split(u, ":") and checking len(parts) == 2 is fragile and can fail or return incorrect results if there are multiple colons (e.g., IPv6 addresses or other non-standard formats). Using strings.LastIndex(u, ":") is much more robust.
| // SSH: git@host:owner/repo | |
| if strings.HasPrefix(u, "git@") { | |
| parts := strings.Split(u, ":") | |
| if len(parts) == 2 { | |
| segments := strings.Split(parts[1], "/") | |
| if len(segments) >= 2 { | |
| return segments[0], segments[1] | |
| } | |
| } | |
| return "", "" | |
| } | |
| // SSH: git@host:owner/repo | |
| if strings.HasPrefix(u, "git@") { | |
| colon := strings.LastIndex(u, ":") | |
| if colon != -1 { | |
| segments := strings.Split(strings.Trim(u[colon+1:], "/"), "/") | |
| if len(segments) >= 2 { | |
| return segments[0], segments[1] | |
| } | |
| } | |
| return "", "" | |
| } |
| return &DiscoveryResult{ | ||
| RepoPath: tempDir, | ||
| Skills: skills, | ||
| Agents: agents, | ||
| Source: source, | ||
| CommitHash: commitHash, | ||
| }, nil |
There was a problem hiding this comment.
In the Gitea Contents API fast path, the returned DiscoveryResult does not include Warnings: warnings. Any warnings accumulated from the failed sparse checkout will be silently discarded and not reported to the user.
return &DiscoveryResult{
RepoPath: tempDir,
Skills: skills,
Agents: agents,
Source: source,
CommitHash: commitHash,
Warnings: warnings,
}, nil| // isCNBHost returns true if the host should be treated as a CNB instance. | ||
| func isCNBHost(host string, extraHosts []string) bool { | ||
| return strings.Contains(host, "cnb.cool") || hostMatchesAny(host, extraHosts) | ||
| } | ||
|
|
||
| // isGiteaHost returns true if the host should be treated as a Gitea instance. | ||
| func isGiteaHost(host string, extraHosts []string) bool { | ||
| return strings.Contains(host, "gitea") || hostMatchesAny(host, extraHosts) | ||
| } |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4112b9d47e
ℹ️ 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".
| if strings.Contains(host, "cnb.cool") { | ||
| return PlatformCNB | ||
| } | ||
| if strings.Contains(host, "gitea") { | ||
| return PlatformGitea |
There was a problem hiding this comment.
Use configured CNB/Gitea hosts in platform detection
The new cnb_hosts / gitea_hosts configuration never affects token selection because detectPlatform only matches literal host substrings (cnb.cool, gitea). For self-hosted instances like git.company.internal, this returns PlatformUnknown, so resolveToken ignores CNB_TOKEN/GITEA_TOKEN and private installs fail unless users also set SKILLSHARE_GIT_TOKEN. This contradicts the advertised self-hosted support and is triggered whenever the host does not include those hard-coded substrings.
Useful? React with 👍 / 👎.
Replace url.PathEscape(path) with escapeGiteaPath() that escapes each path segment individually, preserving directory separators. The previous approach would encode slashes as %2F, breaking nested subdirectory downloads via the Gitea Contents API.
- fix(gitea): use shortHash instead of undefined shortCommitHash - fix(gitea): use strings.LastIndex for robust SSH URL parsing - fix(discovery): preserve warnings in Gitea API discovery fast path - feat(auth): add detectPlatformFromHost with configured host support
runkids
left a comment
There was a problem hiding this comment.
Thanks for the PR — the CNB / Gitea direction is great 👍 A few things I'd like to see addressed before merging:
1. cnb_hosts / gitea_hosts config currently has no effect
ParseOptions.CNBHosts / GiteaHosts are populated from config (cmd/skillshare/install.go), but ParseSourceWithOptions never consumes them — compare GitLab/Azure, which do (isGitLabHost(host, opts.GitLabHosts) and isAzureHost(..., opts.AzureHosts) in source.go). CNB/Gitea have no equivalent branch.
Also, detectPlatformFromHost / isCNBHost / isGiteaHost have no callers — detectPlatformFromHost is never called, and the other two are only called by it — so the whole block is dead code.
Net effect: public gitea.com / cnb.cool still install because they fall through the default owner/repo parsing, but self-hosted instances (hostnames not containing gitea / cnb.cool) aren't detected — no token auth, no API fast path, and the *_hosts config is effectively inert.
Either:
- wire
ParseSourceWithOptionsup toisCNBHost(host, opts.CNBHosts)/isGiteaHost(host, opts.GiteaHosts)so self-hosted support actually works, or - drop
detectPlatformFromHostand the*_hostsconfig for now so the implementation matches what's advertised, and add it back later.
2. Commit hash error is swallowed
In downloadGiteaDir (gitea_download.go):
commitHash, err := giteaFetchLatestCommitHash(...)
if err != nil {
return "", nil // empty hash + nil error
}On failure this returns an empty hash with no error, so the install records an empty commit and later skillshare update / check can't diff against it. Please return err or handle it explicitly.
3. Some tests
The new gitea_download.go and the host-detection helpers aren't covered. At minimum, a source parse test (self-hosted host → correct CloneURL/Subdir) would help — it would also have caught point 1.
Nit: in giteaAPIBase, the host == "gitea.com" special case returns the same value as the general branch, so it can be removed.
Summary
This PR adds CNB (Cloud Native Base) and Gitea platform support to skillshare, along with a comprehensive Chinese README translation.
Changes
CNB Platform Support (cnb.cool)
cnb.cooland custom CNB instancesCNB_TOKENenvironment variable support for git authcnb_hostsin config.yaml andSKILLSHARE_CNB_HOSTSenv var/-/treepaths)Gitea Platform Support
gitea.comand self-hosted Gitea instancesGITEA_TOKENenvironment variable support for git authgitea_hostsandSKILLSHARE_GITEA_HOSTSGET /api/v1/repos/{owner}/{repo}/contents/{path}, avoiding full git clone when sparse checkout is unavailableChinese README (README-zh.md)
Files Changed
internal/install/auth.go(+PlatformCNB, +PlatformGitea, +token env vars)internal/config/config.go,internal/config/project.go(cnb_hosts,gitea_hosts)internal/install/source.go(+isCNBHost, +isGiteaHost, +ParseOptions)internal/install/gitea_download.go(new, Gitea Contents API)internal/install/install_apply.go,internal/install/install_discovery.gocmd/skillshare/install.goREADME-zh.md(new)Verification
https://cnb.cool/org/repo/skills→ correct CloneURL + Subdirhttps://gitea.com/owner/repo/skills/foo→ correct CloneURL + SubdirCNB_TOKENandGITEA_TOKENread from envgitea.com/gitea/act_runnerdry-run discovered 3 agents successfullycnb.cool/zishuo/coding-abyss/skillsdry-run discovered 80 skillscnb_hosts/gitea_hostsnormalization and env var merge