2026-02-27 09:40:43 -03:00
# Skills Catalog Module Documentation
## Purpose
2026-08-20 01:40:10 +03:00
This module provides skill discovery, scanning, and installation capabilities for OpenCode. It supports skill sources backed by git repositories, with caching and conflict resolution for skill installation.
2026-02-27 09:40:43 -03:00
## Entrypoints and structure
- `packages/web/server/lib/skills-catalog/` : Skills catalog module directory containing all skill-related functionality.
- `cache.js` : In-memory cache for scan results with TTL support.
2026-08-20 01:40:10 +03:00
- `curated-sources.js` : Predefined skill sources (Anthropic, OpenAI, Cursor, Matt Pocock).
- `github-meta.js` : Best-effort GitHub repository metadata (stars, last push) with in-memory TTL cache.
2026-02-27 09:40:43 -03:00
- `git.js` : Git operations helpers for cloning and auth error detection.
2026-05-13 12:49:31 +05:30
- `install.js` : Skills installation from git repositories.
- `scan.js` : Skills scanning from git repositories.
- `source.js` : Source string parsing for git repositories.
2026-02-27 09:40:43 -03:00
## Public API
The following functions are exported and used by the web server:
### Cache (`cache.js`)
- `getCacheKey({ normalizedRepo, subpath, identityId })` : Generate cache key for scan results.
- `getCachedScan(key)` : Retrieve cached scan result if not expired.
2026-08-20 01:40:10 +03:00
- `setCachedScan(key, value, ttlMs)` : Store scan result with TTL (default 3 hours).
- `scanWithCache(key, loader, { refresh })` : Run a scan loader with cache lookup, in-flight deduplication, and a global concurrency limit (2 concurrent scans); only `ok: true` results are cached.
2026-02-27 09:40:43 -03:00
- `clearCache()` : Clear all cached scan results.
2026-08-20 01:40:10 +03:00
- Scan results persist to `skills-catalog-cache.json` in the OpenChamber data dir (debounced, atomic rename) and survive server restarts within the TTL.
2026-02-27 09:40:43 -03:00
### Curated Sources (`curated-sources.js`)
2026-08-20 01:40:10 +03:00
- `getCuratedSkillsSources()` : Return list of curated skill sources (Anthropic, OpenAI, Cursor, Matt Pocock).
2026-02-27 09:40:43 -03:00
- `CURATED_SKILLS_SOURCES` : Constant array of predefined sources.
2026-08-20 01:40:10 +03:00
### GitHub Repository Metadata (`github-meta.js`)
- `fetchGitHubRepoMetas(normalizedRepos)` : Fetch `{ stars, repoUpdatedAt }` for GitHub `owner/repo` strings. Best-effort: failures resolve to `null` ; in-flight requests deduplicate; results cached in memory and on disk (`skills-github-meta.json` ) for three hours.
- `clearGitHubMetaCache()` : Test-only cache reset.
2026-02-27 09:40:43 -03:00
### Source Parsing (`source.js`)
2026-05-13 12:49:31 +05:30
- `parseSkillRepoSource(source, { subpath })` : Parse git repository source string into structured object with SSH/HTTPS clone URLs, normalized repo, and effective subpath. Supports SSH URLs, HTTPS URLs, and shorthand `owner/repo[/subpath]` format.
2026-02-27 09:40:43 -03:00
### Git Repository Scanning (`scan.js`)
2026-05-13 12:49:31 +05:30
- `scanSkillsRepository({ source, subpath, defaultSubpath, identity })` : Scan git repository for skills by cloning and analyzing SKILL.md files. Returns array of skill items with metadata.
2026-02-27 09:40:43 -03:00
### Git Repository Installation (`install.js`)
2026-05-13 12:49:31 +05:30
- `installSkillsFromRepository({ source, subpath, defaultSubpath, identity, scope, targetSource, workingDirectory, userSkillDir, selections, conflictPolicy, conflictDecisions })` : Install skills from git repository. Supports user/project scopes, opencode/agents targets, conflict resolution (prompt/skipAll/overwriteAll), and sparse checkout for efficiency.
2026-02-27 09:40:43 -03:00
## Internal Helpers
The following functions are internal helpers used by exported functions:
### Git Helpers (`git.js`)
- `runGit(args, options)` : Execute git command with optional SSH identity, timeout, and max buffer. Returns `{ ok, stdout, stderr, message, code, signal }` .
- `looksLikeAuthError(message)` : Detect if error message indicates authentication failure (permission denied, publickey, etc.).
- `assertGitAvailable()` : Check if git is available in PATH.
2026-08-20 01:40:10 +03:00
### Skill Name Validation (used in `install.js`, `scan.js`)
2026-02-27 09:40:43 -03:00
- `validateSkillName(skillName)` : Validate skill name against pattern `/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/` (1-64 chars, lowercase alphanumeric with hyphens).
2026-08-20 01:40:10 +03:00
### File System Helpers (`install.js`, `scan.js`)
2026-02-27 09:40:43 -03:00
- `safeRm(dir)` : Safely remove directory recursively (ignores errors).
- `ensureDir(dirPath)` : Ensure directory exists with recursive creation.
- `copyDirectoryNoSymlinks(srcDir, dstDir)` : Copy directory contents without symlinks, with path traversal protection.
- `normalizeUserSkillDir(userSkillDir)` : Normalize user skill directory path (handles legacy `~/.config/opencode/skill` → `~/.config/opencode/skills` migration).
### Git Clone Helpers (`install.js`, `scan.js`)
2026-05-13 12:49:31 +05:30
- `cloneRepo({ cloneUrl, identity, tempDir })` : Clone git repository with preferred partial clone (`--filter=blob:none` ) and fallback. Uses non-interactive mode.
2026-02-27 09:40:43 -03:00
### SKILL.md Parsing (`scan.js`)
- `parseSkillMd(content)` : Parse YAML frontmatter from SKILL.md content. Returns `{ ok, frontmatter, warnings }` .
### Path Helpers (`install.js`)
- `toFsPath(repoDir, repoRelPosixPath)` : Convert POSIX path to filesystem path.
- `getTargetSkillDir({ scope, targetSource, workingDirectory, userSkillDir, skillName })` : Determine target installation directory based on scope (user/project), targetSource (opencode/agents), and skill name.
## Response Contracts
### Scan Skills Repository Response
- `ok` : Boolean indicating success.
2026-05-13 12:49:31 +05:30
- `normalizedRepo` : Normalized repo string (`owner/repo` ).
2026-02-27 09:40:43 -03:00
- `effectiveSubpath` : Effective subpath used for scanning (may be from source string or defaultSubpath).
- `items` : Array of skill items with `{ repoSource, repoSubpath, skillDir, skillName, frontmatterName, description, installable, warnings }` .
- `error` : Error object with `{ kind, message }` on failure.
### Install Skills Response
- `ok` : Boolean indicating success.
- `installed` : Array of installed skills with `{ skillName, scope, source }` .
- `skipped` : Array of skipped skills with `{ skillName, reason }` .
- `error` : Error object with `{ kind, message, conflicts? }` on failure. Kinds: `authRequired` , `networkError` , `conflicts` , `invalidSource` , `unknown` .
### Parse Source Response
- `ok` : Boolean indicating success.
2026-05-13 12:49:31 +05:30
- `host` : Git host (e.g., `github.com` , `gitlab.com` ).
2026-02-27 09:40:43 -03:00
- `owner` : Repository owner.
- `repo` : Repository name.
- `cloneUrlSsh` : SSH clone URL.
- `cloneUrlHttps` : HTTPS clone URL.
- `effectiveSubpath` : Subpath for scanning (from source string or options).
- `normalizedRepo` : Normalized repo string (`owner/repo` ).
- `error` : Error object with `{ kind, message }` on failure.
## Notes for Contributors
### Adding a New Skill Source
1. Create a new subdirectory under `packages/web/server/lib/skills-catalog/` (e.g., `newsource/` ).
2. Implement `scan.js` with a function that returns `{ ok, items, error? }` matching the SkillsCatalogItem contract.
3. Implement `install.js` with a function that accepts selections and returns `{ ok, installed, skipped, error? }` .
4. Add the source to `CURATED_SKILLS_SOURCES` in `curated-sources.js` if it should appear in the default catalog.
5. Update `packages/web/server/index.js` to import and wire up the new source.
### Skill Name Validation
- All skill names must match `/^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/` (1-64 chars).
2026-08-20 01:40:10 +03:00
- Skill names are derived from directory basenames for git repos.
2026-02-27 09:40:43 -03:00
- Invalid names result in non-installable skills with appropriate warnings.
### Git Cloning Strategy
- Use sparse checkout to minimize clone size: `sparse-checkout init` , `sparse-checkout set` , `checkout HEAD` .
- Preferred clone uses `--depth=1 --filter=blob:none` for partial clone with fallback to `--depth=1` .
- Always use non-interactive mode (`GIT_TERMINAL_PROMPT=0` ) to avoid hangs.
- SSH keys are injected via `core.sshCommand` in git config.
### Conflict Resolution
- Installation checks for existing skills before downloading/cloning.
- Three conflict policies: `prompt` , `skipAll` , `overwriteAll` .
- Per-skill decisions override global policy via `conflictDecisions` map.
- Conflict response includes `{ skillName, scope, source }` for each conflict.
### Cache Management
- Cache keys include `normalizedRepo` , `subpath` , and `identityId` for isolation.
2026-08-20 01:40:10 +03:00
- Default TTL is 3 hours for both scan results and GitHub repository metadata.
- Scan and GitHub metadata caches persist to JSON files in the OpenChamber data dir, so app restarts and page refreshes reuse previous results instead of re-hitting GitHub.
- Scans run through a global concurrency limiter (2 at a time) with per-key in-flight deduplication.
- The refresh button passes `refresh: true` and bypasses the cache.
2026-02-27 09:40:43 -03:00
### Security Considerations
- Path traversal protection in `copyDirectoryNoSymlinks` : resolves real paths and checks containment.
- Symlinks are explicitly rejected to prevent escape from skill directory.
- SSH key paths are trimmed but not escaped in `git.js` (assumes safe input from profiles).
- Temporary directories are cleaned up in `finally` blocks.
### Error Handling
- All exported functions return `{ ok, ... }` result objects, not throw.
- Error kinds: `authRequired` , `networkError` , `conflicts` , `invalidSource` , `unknown` .
- Use `looksLikeAuthError` to detect SSH/HTTPS auth failures for better UX.
- Log errors to console for debugging but return structured errors to callers.
### Testing
- Run `bun run type-check` , `bun run lint` , and `bun run build` before finalizing changes.
- Consider edge cases: non-existent repos, private repos without auth, missing SKILL.md files, invalid skill names, conflicts, network failures.
## Verification Commands
- Type-check: `bun run type-check`
- Lint: `bun run lint`
- Build: `bun run build`