Skip to content

feat: add CNB & Gitea platform support, Chinese README#168

Open
DarkiT wants to merge 6 commits into
runkids:mainfrom
DarkiT:develop
Open

feat: add CNB & Gitea platform support, Chinese README#168
DarkiT wants to merge 6 commits into
runkids:mainfrom
DarkiT:develop

Conversation

@DarkiT
Copy link
Copy Markdown

@DarkiT DarkiT commented May 26, 2026

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)

  • Platform detection for cnb.cool and custom CNB instances
  • CNB_TOKEN environment variable support for git auth
  • Configuration via cnb_hosts in config.yaml and SKILLSHARE_CNB_HOSTS env var
  • Standard HTTPS git clone and subdirectory skill discovery
  • Web URL parsing (including /-/tree paths)

Gitea Platform Support

  • Platform detection for gitea.com and self-hosted Gitea instances
  • GITEA_TOKEN environment variable support for git auth
  • Configuration via gitea_hosts and SKILLSHARE_GITEA_HOSTS
  • Gitea Contents API fast path — directory download via GET /api/v1/repos/{owner}/{repo}/contents/{path}, avoiding full git clone when sparse checkout is unavailable
  • Falls back gracefully: sparse checkout → Gitea API → full clone

Chinese README (README-zh.md)

  • Full Chinese translation of the project README
  • Updated version reference to v0.19.23
  • All links, badges, and formatting preserved

Files Changed

Layer Files
Auth internal/install/auth.go (+PlatformCNB, +PlatformGitea, +token env vars)
Config internal/config/config.go, internal/config/project.go (cnb_hosts, gitea_hosts)
URL Parse internal/install/source.go (+isCNBHost, +isGiteaHost, +ParseOptions)
API Download internal/install/gitea_download.go (new, Gitea Contents API)
Install Flow internal/install/install_apply.go, internal/install/install_discovery.go
CLI cmd/skillshare/install.go
Docs README-zh.md (new)

Verification

  • ✅ URL parsing: https://cnb.cool/org/repo/skills → correct CloneURL + Subdir
  • ✅ URL parsing: https://gitea.com/owner/repo/skills/foo → correct CloneURL + Subdir
  • ✅ Platform detection: CNB and Gitea identified correctly
  • ✅ Token resolution: CNB_TOKEN and GITEA_TOKEN read from env
  • ✅ End-to-end: gitea.com/gitea/act_runner dry-run discovered 3 agents successfully
  • ✅ End-to-end: cnb.cool/zishuo/coding-abyss/skills dry-run discovered 80 skills
  • ✅ Config: cnb_hosts / gitea_hosts normalization and env var merge
  • ✅ No regressions: all existing platform tests pass

DarkiT added 4 commits May 26, 2026 19:00
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
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

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.

Comment thread internal/install/gitea_download.go Outdated
if err != nil {
return "", nil
}
return shortCommitHash(commitHash), nil
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

The function shortCommitHash is not defined in this package, which will cause a compilation error. It should be shortHash instead, which is defined in install_git.go.

Suggested change
return shortCommitHash(commitHash), nil
return shortHash(commitHash), nil

Comment thread internal/install/gitea_download.go Outdated
Comment on lines +78 to +79
contentsURL := fmt.Sprintf("%s/repos/%s/%s/contents/%s",
strings.TrimRight(apiBase, "/"), owner, repo, url.PathEscape(path))
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

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, "/"))

Comment on lines +263 to +273
// 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 "", ""
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

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.

Suggested change
// 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 "", ""
}

Comment on lines +406 to +412
return &DiscoveryResult{
RepoPath: tempDir,
Skills: skills,
Agents: agents,
Source: source,
CommitHash: commitHash,
}, nil
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

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

Comment on lines +467 to +475
// 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)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The functions isCNBHost and isGiteaHost are defined but never used anywhere in the codebase. Since they are dead code, they should be removed to keep the codebase clean.

Copy link
Copy Markdown

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

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

Comment thread internal/install/auth.go
Comment on lines +67 to +71
if strings.Contains(host, "cnb.cool") {
return PlatformCNB
}
if strings.Contains(host, "gitea") {
return PlatformGitea
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 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 👍 / 👎.

DarkiT added 2 commits May 26, 2026 19:21
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 runkids self-requested a review May 27, 2026 02:54
@runkids runkids self-requested a review May 27, 2026 05:53
Copy link
Copy Markdown
Owner

@runkids runkids left a comment

Choose a reason for hiding this comment

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

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 ParseSourceWithOptions up to isCNBHost(host, opts.CNBHosts) / isGiteaHost(host, opts.GiteaHosts) so self-hosted support actually works, or
  • drop detectPlatformFromHost and the *_hosts config 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.

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.

2 participants