npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

opencode-skill-autodiscovery

v2.1.2

Published

OpenCode plugin that auto-discovers skills installed by VS Code agent plugins, Claude Code plugins, and Agent Plugins 1.0.0 packages, and registers them with the local opencode config.

Readme

opencode-skill-autodiscovery

An opencode plugin that auto-discovers skills installed by VS Code agent plugins, Claude Code plugins, and Agent Plugins 1.0.0 conformant packages on the current machine, and registers them with opencode's skills.paths so they appear in every session. Each discovered skill is also exposed as a /skill-name slash command that loads the skill and routes the rest of your message through it.

Why

Skills live in different places depending on the tool, OS, and local vs remote setup:

  • VS Code agent plugins, local client: ~/.vscode/agent-plugins (Windows / older builds), ~/.config/Code/agentPlugins (Linux), ~/Library/Application Support/Code/agentPlugins (macOS), and %APPDATA%\Code\agentPlugins (Windows), plus Code - Insiders variants. The install layout is {host}/{org}/{repo}, with optional installed.json and cache.json manifests alongside.
  • VS Code agent plugins on a remote host (Remote-SSH, Codespaces, Dev Containers, WSL): ~/.vscode-server/data/agentPlugins. VS Code syncs the enabled skills from your local client into a synthetic "VS Code Synced Data" plugin materialized at ~/.vscode-server/data/agentPlugins/{sanitizedUri}/{nonce}/skills/..., and records each synced bundle in ~/.vscode-server/data/agentPlugins/cache.json.
  • Claude Code plugins: ~/.claude/plugins/installed_plugins.json
  • Claude Code plugins on a remote host (SSH/remote sessions): ~/.claude/remote/plugins/installed_plugins.json
  • Agent Plugins 1.0.0 packages distributed via npm. opencode installs npm plugins into its own cache (~/.cache/opencode/packages/...), not the project's node_modules, and a skills.paths entry relative to the project dir silently resolves to nothing. This plugin scans both the opencode plugin cache and the project's node_modules for packages carrying a root plugin.json whose $schema is https://agent-plugins.org/schemas/..., and registers their skills/ with absolute paths — so an npm-distributed Agent Plugins package (e.g. @dodopayments/opencode-plugin) just works with "plugin": ["opencode-skill-autodiscovery"] and nothing else.

Each points at plugin directories that contain SKILL.md files. This plugin reads the manifests (including the remote cache.json LRU), resolves each entry to its on-disk skill directories, and registers them with opencode. When a discovered package carries a conformant root plugin.json, that manifest is preferred (its skills/ children are registered as-is); otherwise the plugin falls back to a tree walk so legacy layouts keep working.

MCP servers (opt-in)

With the mcp option, the plugin also reads each discovered package's mcp.json and maps it onto opencode's config.mcp:

| Agent Plugins server | opencode entry | |---|---| | { "type": "stdio", "command", "args", "env" } | { "type": "local", "command": [...], "environment": {...} } | | { "type": "streamable-http", "url" } | { "type": "remote", "url" } | | { "type": "sse" } | skipped (opencode has no SSE transport) |

${PLUGIN_ROOT} and ${PLUGIN_DATA} placeholders are expanded in args/env, and both variables are injected into each stdio server's environment (PLUGIN_DATA points at {opencode state}/plugin-data/{packageName}). Invalid entries are skipped per-entry, never fatally. sse servers and stdio cwd (which opencode cannot represent) are dropped with a log line.

MCP credentials

Package-declared mcp.json servers are mirrored verbatim into your opencode config: headers, env values, and server urls are copied exactly as written (modulo ${PLUGIN_ROOT} / ${PLUGIN_DATA} expansion) into config.mcp.<server>. opencode stores config.mcp in plaintext, so any credential in those fields — an Authorization header, a token in env, an userinfo@ URL — is plaintext-at-rest and may appear in host config backups, logs, or terminal output. Treat a server's headers/env/URL as a static snapshot: once mirrored, the value has no rotation or revocation linkage back to the package — updating the package does not rotate a copied token.

The classifier flags a header or env name that matches the credential name rule (a well-known header word such as authorization/token, or the AUTH/_KEY/_TOKEN/_SECRET/_CREDENTIAL-style suffixes an env var carries, e.g. GITHUB_TOKEN, OPENAI_API_KEY), and a header/env value that carries a bearer/secret word, a known provider prefix (sk-, ghp_, xox), or a high-entropy/base64 token band (a 40-char opaque value counts even under an unlisted name like HUB_ID).

Guidance:

  • Use https-only remotes. streamable-http servers must already be https:// to register (a plain http:// URL would ship any headers in cleartext), and that is the transport you want when a server carries credentials.
  • Audit config.mcp after discovery to confirm which servers and credential fields entered your config, and remove anything you did not intend to persist.
  • Use consent / exclude to control admission: servers from untrusted packages are skipped until the package is listed under consent.mcp, and exclude drops a package entirely.
  • The planning-time mkdir for a package stdio server's PLUGIN_DATA directory is deferred: the directory is created only when the server is actually applied to the config, never at plan time — so a discovered-but-unapplied server leaves no filesystem trace.

Agents (opt-in)

Agent Plugins 1.0.0 has no portable "agents" component type (only skills and MCP servers), so agents are contributed through the spec's client-extension mechanism. With the agents option, the plugin reads agents from four sources, in order:

  1. plugin.json → extensions["dev.opencode"].agents (a map of agent name → opencode agent config).
  2. A dev.opencode/agents/<name>.json extension directory (one opencode agent config per file).
  3. A legacy Claude Code plugin shim: .claude-plugin/plugin.json agents, mapping description and systemPrompt (or the agents/<name>/AGENTS.md body) onto an opencode agent.
  4. Flat agents/<name>.md files (the agency-agents layout) — one agent per file, keyed by filename, with description/color from frontmatter and the markdown body as the prompt.

Agents discovered without an explicit mode default to opencode's all, so they are both Tab-selectable and spawnable as subagents.

Discovered agents are registered as config.agent.<name> with the same rules as commands: user-defined agents are never overwritten, same-source mirrors are collapsed, and cross-source collisions become <package>-<agent>.

Trust note: package-supplied capability is clamped to the conservative default: permission blocks and tools grants are always dropped, and any declared mode other than subagent clamps to subagent. Each drop is logged naming the package and the agent. If you need more, define the agent yourself in opencode.json (which always wins).

Slash commands

opencode treats skills and slash commands as separate mechanisms: skills are only loaded on demand via the skill tool. To make a discovered skill invocable as /name, the plugin also registers a command for it (via config.command) whose template loads the skill and forwards your arguments:

Load the "spec" skill and follow its instructions.
Context: $ARGUMENTS

The skill name is rendered only as a quoted data value in the template — never inside backticks — so a hostile name cannot break out into surrounding instruction text.

So /spec plan the migration loads the spec skill and runs it against plan the migration. The command's description is taken from the skill's frontmatter; existing commands with the same name are never overwritten (a colliding skill from a different source is registered as /package-skill).

Install

Add the package name to the plugin array in your opencode.json:

{
  "plugin": ["opencode-skill-autodiscovery"]
}

Use the tuple form to configure options:

{
  "plugin": [
    [
      "opencode-skill-autodiscovery",
      {
        "extraRoots": ["/home/user/.vscode-server"],
        "scanCache": true,
        "scanNodeModules": true,
        "exclude": ["unwanted-plugin"],
        "mcp": false,
        "agents": false
      }
    ]
  ]
}

| Option | Default | Meaning | |---|---|---| | extraRoots | [] | Extra root directories to scan (e.g. a non-standard VS Code data location on a remote host). | | scanCache | false | Scan opencode's plugin cache (~/.cache/opencode/packages/*) for Agent Plugins packages. | | scanNodeModules | false | Scan the project's node_modules (incl. @scope/*) for Agent Plugins packages. | | exclude | [] | Package names to skip during discovery, regardless of trust tier. Matches the conformant package's plugin.json name, or the directory basename when there is no manifest. | | mcp | false | Also register MCP servers from discovered packages' mcp.json. | | agents | false | Also register agents from packages (see "Agents" above). | | consent | {} | Per-package consent map: consent.mcp / consent.agents list package names whose MCP servers / agents are wanted even when the package is discovered as untrusted. Refines the mcp / agents switches; exclude remains the deny side. |

Both scan flags default to false for supply-chain reasons: a discovered skill's SKILL.md becomes prompt material in your sessions, so anything that plants a skill plants model-facing instructions — and neither transitive npm dependencies (which land in node_modules without any install script running) nor the shared ~/.cache/opencode/packages directory (populated by every project on the machine) requires a manifest you ever reviewed. This is the OWASP LLM01 risk arriving via the software supply chain; both sources are therefore opt-in, while manifest-mediated sources (Claude Code plugins, VS Code agent plugins) stay default-on because their manifests record deliberate, host-vouched installs.

Threat model

Discovery reads manifests from well-known locations and registers what it finds into your session config. Not all sources are equally trustworthy, so they are split into two tiers:

Trusted by default — content a host tool or opencode itself installed deliberately, vouched for by a manifest:

  • Claude Code plugins (installed_plugins.json, local and remote)
  • VS Code agent plugins recorded in installed.json / cache.json
  • packages in opencode's own plugin cache (~/.cache/opencode/packages/*), which exist because your config asked opencode to fetch them

Untrusted by default — content present merely as a side effect:

  • the project's node_modules: dependencies install transitively, so anything in the tree can ship skills, MCP servers, or agents. Scanning it is off by default; restore it explicitly with "scanNodeModules": true.
  • user-supplied extraRoots: the plugin cannot vouch for whatever you point it at. A manifest discovered under one of these roots is untrusted and may only reference a package inside that root (a trusted VS Code home root may still reference a global extension directory).
  • manifest-less directory walks (e.g. cloned-but-uninstalled marketplace folders).

Graduated default

Trust gates the two component tiers differently:

  • Skills and slash commands register for every tier. Both are read-only content registration — surfacing a package's skills is the plugin's job, so trust never blocks them. An untrusted package's skills and commands are registered exactly like a trusted one's, but each untrusted package emits a one-line info log naming the package and its source, so side-effect content entering the session stays visible. exclude remains the deny side.
  • MCP servers and agents are default-off for untrusted packages. The mcp / agents switches admit servers and agents from trusted packages directly; an untrusted package contributes them only when the package is listed under consent.mcp / consent.agents.

Trust never decides whether content is safe: a registered skill's SKILL.md becomes prompt material in your sessions. The $schema check identifies format only — never provenance or safety. Any package can copy the literal schema URL, so a conformant manifest proves nothing about who wrote it.

mcp and agents register package content

Both flags are global opt-in switches, narrowed by exclude and refined by per-package consent (below). Trust decides admission: mcp: true / agents: true admit servers or agents from trusted packages, and from untrusted packages only when the package is listed under consent.mcp / consent.agents.

  • mcp: true admits MCP servers from packages that are trusted or listed in consent.mcp. Every registered package-supplied server starts with enabled: false: opencode will not spawn a package-declared binary or connect to a package-chosen endpoint at startup on discovery alone. Admittance is opt-in; the safe default is off.
  • agents: true admits agents from packages that are trusted or listed in consent.agents, then clamps each to the conservative default: a declared mode other than subagent is dropped (a package cannot make itself a primary agent), tools booleans such as "write": true are never inherited, and permission blocks are dropped — each drop is logged naming the package and the agent. An agent without an explicit mode still falls through to opencode's all default.

Pair these flags with exclude to carve out packages you do not want registered:

{
  "plugin": [
    ["opencode-skill-autodiscovery", { "mcp": true, "agents": true, "exclude": ["unwanted-package"] }]
  ]
}

Per-package consent (consent)

Consent is the per-package allow side for packages discovered as untrusted (the project's node_modules, user-supplied extraRoots, manifest-less walks). List a package's name under consent.mcp to admit its MCP servers, or under consent.agents to admit its agents, even though the package is not trusted by default:

{
  "plugin": [
    [
      "opencode-skill-autodiscovery",
      {
        "scanNodeModules": true,
        "mcp": true,
        "agents": true,
        "consent": {
          "mcp": ["community-tools"],
          "agents": ["community-tools"]
        }
      }
    ]
  ]
}
  • consent only refines the coarse switches: mcp: true / agents: true stay the on-switch, and consent.mcp / consent.agents list which untrusted packages are admitted when a switch is on.
  • exclude remains the deny side and wins: a package listed in both exclude and consent is never discovered.
  • Trusted packages need no consent entry.
  • The default — no consent option — grants nothing extra: untrusted packages contribute no MCP servers or agents until admitted by name.

Consent is declarative (package names in opencode.json), so it works inside opencode's synchronous config hook — no interactive prompt required.

Identifier rules

Every package-supplied identifier that becomes a config key must match the same pattern: lowercase letters, digits, -, and . only ([a-z0-9.-]), starting and ending with an alphanumeric character, with no -- or .. runs. The prototype-chain keys __proto__ and constructor are rejected outright even though they satisfy the character pattern. This applies to:

  • the plugin.json manifest name (collision namespaces and exclude matching)
  • SKILL.md frontmatter name (slash-command keys)
  • mcp.json server keys (config.mcp keys)
  • agent manifest keys, dev.opencode/agents/*.json filenames, legacy shim agent names, and flat agents/<name>.md filenames (config.agent keys)

An invalid identifier never aborts discovery: the offending entry is skipped with a log line naming the package, its source, and a reason, and legitimate entries from the same package still register.

Defense-in-depth at the write sites holds even if a future call site skips those checks:

  • The config.command, config.mcp, and config.agent containers are built prototype-free (Object.create(null)), so no key can ever resolve to an inherited member such as toString, and __proto__/constructor can never take effect through inheritance.
  • The skill name inside a slash-command template is rendered as a quoted data value (JSON string encoding), never wrapped in backticks, so it cannot break out into surrounding instruction text.
  • Frontmatter description strings pass through the same control-character sanitizer used for log lines before they enter config.

Migrating from 1.x

scanNodeModules used to default to true; it now defaults to false. If you distribute an Agent Plugins package via npm and consumers relied on it being picked up from the project's node_modules, they must now opt in explicitly:

{
  "plugin": [["opencode-skill-autodiscovery", { "scanNodeModules": true }]]
}

Prefer shipping your package as a regular opencode plugin ("plugin": ["your-package"]) instead: opencode installs it into its own cache, where you can pick it up explicitly with "scanCache": true — no scan of the dependency tree required.

VPS / remote hosts (SSH sessions)

Discovery is machine-local: a remote session only sees the skills, agents, and packages installed on that host. So opencode (and this plugin) must be installed on each remote machine, and you run opencode inside the SSH session — not from a local client terminal.

On each remote host:

# 1. Install opencode if it isn't there yet
npm install -g opencode-ai

# 2. Install this plugin globally. The -g flag targets the machine-wide config;
#    without it, opencode writes a project-scoped .opencode/opencode.json
#    instead (easy to miss, because the plugin looks installed but only for
#    that one directory).
opencode plugin opencode-skill-autodiscovery -g

# 3. Restart opencode. It fetches the latest published version into the
#    remote's own cache.

Or hand-edit the remote's global config (~/.config/opencode/opencode.json):

{
  "plugin": [["opencode-skill-autodiscovery", { "mcp": true, "agents": true }]]
}

What a remote session discovers (that host's own installs):

  • VS Code Remote-SSH synced skills from ~/.vscode-server/data/agentPlugins/ (read via the cache.json LRU).
  • Claude Code remote plugins from ~/.claude/remote/plugins/.
  • Agent Plugins packages in the remote's opencode cache (~/.cache/opencode/packages/*) and the project's node_modules.

Caveats:

  • Run opencode on the remote. A local client terminal reads the local machine's manifests, not the remote's.
  • VS Code only syncs enabled skills to the server as a flattened "VS Code Synced Data" bundle; marketplace clones (including their agents/*.md files) are generally not copied. Install the pack on the remote too if you want its agents there.
  • To force a re-fetch of a new release on a remote, clear the cached copy and restart (opencode re-downloads the latest):
    rm -rf ~/.cache/opencode/packages/opencode-skill-autodiscovery*

Notes

  • Discovery is inherently machine-local: it reads manifests from the home directory of the machine opencode is running on. Remote sessions on a different machine will discover that machine's skills.
  • On a remote host, VS Code does not copy your marketplace plugins over. It syncs only the enabled skills/agents/etc. from your client as a single flattened "VS Code Synced Data" bundle under ~/.vscode-server/data/agentPlugins/. That is why you won't see the original {org}/{repo} layout on the remote — the skill directories are named after the skills instead. This plugin reads cache.json to locate those materialized bundles.
  • Newer VS Code layouts have no installed.json at all; marketplaces are cloned directly under the agent plugin dir. The plugin falls back to a tree walk only when no manifest is present, so it never surfaces skills from cloned-but-uninstalled marketplaces on setups that do have a manifest.
  • VS Code account sync only syncs your marketplace extension list; each machine still has its own installed.json/cache.json that this plugin reads.
  • A package is only treated as an Agent Plugins package when its root plugin.json declares a $schema under https://agent-plugins.org/schemas/. Everything else is ignored by the cache/node_modules scanners and falls back to the tree walk elsewhere. This check identifies format only — never provenance or safety: any package can copy the literal schema URL, so a conformant manifest does not make scanning an untrusted directory safe. Content is trusted based on where it came from (host-installed manifests vs. side-effect locations), not on its shape.
  • The same plugin can be materialised in several VS Code layouts at once (local clone + synced bundle + marketplace clone). skills.paths keeps all paths; slash commands and MCP servers are de-duplicated so mirrors don't create a wall of /mirror-of-... junk.
  • The plugin is a no-op when no manifests or conformant packages exist, or when they contain no skills.

Development

npm install
npm run build   # tsc -> dist/
npm test        # build + node --test (fixture-based unit tests)

Publish:

npm publish --access public