anomalyco / anomalyco/opencode

[FEATURE]: Load skills from a Git repository (reuse the references git source)

Open
#39,385 3 comments 4 reactions 1 assignee View on GitHub

@rekram1-node is already working on this.

Since Jul 28, 2026.

Dominant language
TypeScript
Stars
209k
Forks
27.5k
PR merge metrics
PR metrics pending

Description

Feature hasn't been suggested before.
  • I have verified this feature I'm about to request hasn't been suggested before.
Describe the enhancement you want to request

Summary: let skills accept a Git repository as a source, using the same shape references already accepts.

The gap

As of 1.18.7, a skill source is either a local directory or an HTTP base URL serving an index.json ({ skills: [{ name, files, version? }] }) — spelled skills.paths / skills.urls in the v1 config and inferred from each entry in the v2 array form.

Neither works well for a skill collection kept in a private Git repository, which is a natural place to keep them: versioned, reviewed, and already how most people distribute skills to other agents.

  • A directory source requires every machine to have a checkout at a path written into opencode.json. That makes the config machine-specific, so it can't be shared through dotfiles as-is.
  • A URL source performs a plain HTTP GET with no Authorization header, so a private raw.githubusercontent.com URL returns 404. It also requires authoring and maintaining an index.json next to the skills.
Why this should be cheap

opencode already has the pieces:

  • references accepts { repository, branch } and documents support for Git URLs, host/path references, and owner/repo shorthand.
  • Repository / RepositoryCache already clone, cache, and refresh those repositories.

So this is mostly wiring an existing source shape and an existing clone path into skill discovery.

Proposed config

In the v2 config, skills is a list of strings whose kind is inferred from the value (config/plugin/skill.ts treats an http(s) URL as a UrlSource and anything else as a path). A git source could follow the same rule, reusing the shorthand references already accepts:

{
  "skills": ["my-org/agent-skills#main"]
}

An explicit object form works equally well if inference is considered too magical:

{
  "skills": [{ "repository": "my-org/agent-skills", "branch": "main" }]
}

Either way: clone into the repository cache, then scan the checkout exactly the way a directory source is scanned. Refresh via fetch instead of the index.json version field.

Benefits
  • Authentication is delegated to git. SSH keys, credential helpers, deploy keys, and self-hosted hosts all work with no new code, and no tokens end up in opencode.json.
  • Portable config. No machine-specific absolute paths, and no requirement that a checkout already exists on the machine.
  • No index.json to author or keep in sync — the repository is the manifest.
  • Works for GitLab / Gitea / enterprise hosts, not just public HTTP endpoints.
Implementation sketch

Reading main, the whole thing appears to hang off one line — packages/core/src/skill.ts:

const directories = source.type === "directory" ? [source.path] : yield* discovery.pull(source.url)

A git source would be a third branch there. What that branch needs already exists and is exercised by packages/core/src/reference.ts, which does Repository.parseRepository.validateBranchRepository.cachePathRepositoryCache.ensure({ reference, branch, refresh: true }).

Rough shape:

File Change
packages/schema/src/skill.ts Add a git member to the Source tagged union alongside directory / url / embedded, plus its equals / key branches. packages/schema/src/reference.ts already defines a GitSource to model it on.
packages/core/src/skill.ts Third branch in load(); resolve the checkout directory via RepositoryCache and fall through to the existing glob. Adds RepositoryCache / Global to the layer deps.
packages/core/src/config/plugin/skill.ts Recognise the git form and emit the new source.
tests Extend the existing skill / skill-discovery tests.

That looks like roughly 100 lines plus tests, since the clone, cache-path, and refresh logic are all reused rather than written.

One design question I can't answer from the outside, and the reason this is an issue rather than a PR: reference.ts forks its RepositoryCache.ensure into a scope (Effect.forkIn), so materialization is non-blocking. SkillV2.load instead globs a directory that must already exist. So the first run after adding a git source has to either block on the clone or return no skills and pick them up on a later reload. That is a product decision, so I would rather have the team's call on it before writing anything.

Happy to implement whichever way you prefer if this is something you would take.

Related issues

Listed so this isn't mistaken for a duplicate — these are about the existing skills.urls path, not about adding a git source:

  • #20020 — skills fetched via skills.urls cannot be invoked
  • #33548 — skills.urls entries are cached by existence and never updated
  • #33053 — skills entries are overridden instead of merged across layered configs

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.