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

@sunnygg/distributor

v1.2.0

Published

Synchronize Agent Skills across supported agent harnesses.

Readme

npx @sunnygg/distributor init

Getting started

npx @sunnygg/distributor init
npx @sunnygg/distributor import
npx @sunnygg/distributor sync

init creates the Distributor configuration and prompts you to choose project or global syncing, a skill source, and target agent harnesses. It also scans supported harness directories and offers to import any existing skills it finds.

import scans every native and compatible skill directory declared by the available harness adapters. You can select which discovered skills to copy into the configured source. Existing source skills are preserved. If Distributor has not been initialized, import runs interactive initialization first.

sync links the skills from that source into the configured agent harnesses.

Add your first skill

Initialize a project with the defaults, add a skill, inspect the plan, and sync it:

distributor init --yes

mkdir -p .agents/skills/code-review
cat > .agents/skills/code-review/SKILL.md <<'EOF'
---
name: code-review
description: Review code changes.
---
EOF

distributor sync --dry-run
distributor sync

init --yes creates the default config, .agents/skills, and the local-state ignore file without overwriting existing content. Plain distributor init prompts when run interactively. In a non-interactive terminal, use --yes.

Each non-hidden directory immediately under the source root that contains a regular file named exactly SKILL.md is a skill. Skills with invalid frontmatter or unsafe entries are skipped and reported as warnings by status and sync; other valid skills continue to load. Other non-hidden files and directories are helper content and are preserved at the same relative paths. Each top-level skill or helper directory is linked as one directory; a top-level helper file is linked as a file. Because skill directories are linked intact, optional content such as agents/openai.yaml (or agents/openai.yml) remains visible through every linked skill folder.

Command details

distributor                         # show help
distributor --help                  # show help
distributor help                    # show help
distributor help sync               # show sync help
distributor --version               # print the installed version
distributor -V                      # print the installed version
distributor version                 # print the installed version
distributor init                    # interactive initialization
distributor init --yes              # initialize with defaults
distributor init -y                 # same as --yes
distributor import                  # import skills from harness directories
distributor status                  # show skill and reference status
distributor sync                    # sync every enabled harness
distributor sync --harness codex    # sync one enabled harness
distributor sync --dry-run          # plan without writing
distributor remove                  # remove every managed link

distributor status shows table summaries for source skills, skill-to-placement references per configured harness, skill configuration, and source and harness storage paths. A configured skill uses ; a skill that needs sync or has a conflict uses . The command also reports whether references are up to date. It uses the same read-only planning checks as sync and does not write links, directories, or managed state.

--harness <id> may appear only once and must name an available harness that is enabled in the project config. A dry run follows the same config, skill, adapter, state, target, conflict, and diff-inspection path as a real sync, then stops before the first write-capable operation. It does not create directories, links, .distributor files, or filesystem metadata.

Configuration

Distributor searches upward from the current directory for the nearest distributor.config.json, distributor.config.js, or distributor.config.ts. In a Git worktree, discovery stops at the worktree root. The config's directory is the project root.

The exact config generated by distributor init --yes is:

{
  "scope": "project",
  "source": ".agents/skills",
  "harnesses": [
    { "name": "codex", "useHarnessFolder": true },
    { "name": "claude-code", "useHarnessFolder": true },
    { "name": "opencode", "useHarnessFolder": true },
    { "name": "cursor", "useHarnessFolder": true },
    { "name": "gemini-cli", "useHarnessFolder": true },
    { "name": "antigravity", "useHarnessFolder": true },
    { "name": "github-copilot", "useHarnessFolder": true },
    { "name": "openhands", "useHarnessFolder": true },
    { "name": "pi", "useHarnessFolder": true },
    { "name": "cline", "useHarnessFolder": true },
    { "name": "goose", "useHarnessFolder": true },
    { "name": "crush", "useHarnessFolder": true },
    { "name": "qwen-code", "useHarnessFolder": true },
    { "name": "kilo-code", "useHarnessFolder": true },
    { "name": "roo-code", "useHarnessFolder": true },
    { "name": "trae-agent", "useHarnessFolder": true }
  ]
}

scope defaults to project when omitted. project links skills inside the project. global still reads the source from the project, but links those skills into user-level directories.

source defaults to .agents/skills when omitted. Every harness entry requires name. The optional useHarnessFolder field defaults to false. When it is false, automatic placement prefers the scope's .agents/skills directory when that harness supports it. When it is true, automatic placement uses the harness's native directory. Harnesses without .agents/skills support use their native directory either way, so the boolean has no effect for them.

An explicit targets array takes precedence over useHarnessFolder. The expanded form below shows explicit project and user placements plus a custom project target:

{
  "scope": "project",
  "source": ".agents/skills",
  "harnesses": [
    {
      "name": "codex",
      "useHarnessFolder": true,
      "targets": [{ "placement": "project" }, { "placement": "user" }]
    },
    {
      "name": "claude-code",
      "useHarnessFolder": true,
      "targets": [
        {
          "placement": "project",
          "path": ".custom/claude-skills"
        },
        { "placement": "user" }
      ]
    },
    {
      "name": "opencode",
      "useHarnessFolder": false,
      "targets": [{ "placement": "agents-project" }, { "placement": "user" }]
    }
  ]
}

Configuration rules:

  • harnesses is a non-empty array of objects. Each object requires name and may include a boolean useHarnessFolder, which defaults to false; harness IDs must be unique.
  • A target may contain placement, path, or both. When placement is omitted, the adapter's default project placement supplies its behavior.
  • Relative paths resolve from the project root. ~, $HOME, and $PROJECT_ROOT are supported; other variable syntax is rejected.
  • Only project and explicitly selected user placements are allowed. Admin, system, plugin, package, and configured scopes are rejected.
  • path explicitly overrides the selected placement's target root and is how to choose a custom directory.
  • Unknown fields, placements, adapters, empty target arrays, and duplicate effective targets are errors.

JavaScript and TypeScript configs use the same shape and must default-export their value:

import type { DistributorConfig } from "@sunnygg/distributor";

const config = {
  source: ".agents/skills",
  harnesses: [
    { name: "codex", useHarnessFolder: true },
    { name: "claude-code", useHarnessFolder: true },
    { name: "opencode", useHarnessFolder: true },
  ],
} satisfies DistributorConfig;

export default config;

JavaScript and TypeScript config files are trusted executable code and are loaded by Distributor. Use them only when you trust the project. Skill Markdown, YAML, scripts, references, and assets are read as data and are never executed by Distributor.

Writing custom adapters

A custom adapter teaches Distributor where an otherwise unsupported agent harness reads Agent Skills. The adapter does not run the harness or convert a skill. It names one or more skill directories, and Distributor links each skill's files from the configured source into the selected directory.

Assume the project used in the examples is /work/my-app and commands are run from that directory. The important paths are:

/work/my-app/                              project root
├── distributor.config.json               Distributor project configuration
├── .agents/skills/                        canonical skill source
│   └── code-review/SKILL.md               one source skill
├── .distributor/adapters/                 custom adapters loaded for this run
│   └── team-agent.json                    one adapter definition
└── .team-agent/skills/                    Team Agent's project skill directory
    └── code-review/
        └── SKILL.md -> ../../../.agents/skills/code-review/SKILL.md

Distributor finds the project root from the nearest distributor.config.* file, but it finds custom adapters in .distributor/adapters under the exact working directory. Therefore, run Distributor from /work/my-app in this example. Running it from /work/my-app/packages/api would look for adapters in /work/my-app/packages/api/.distributor/adapters, even if Distributor finds the project configuration higher up.

Only immediate .json, .js, and .ts files are loaded. Nested adapter files and other extensions are ignored. The adapter's name is its harness ID; the adapter filename can be anything.

Quick start: one project directory with JSON

JSON is the shortest option and does not execute code. From the project root, create .distributor/adapters/team-agent.json:

{
  "name": "team-agent",
  "displayName": "Team Agent",
  "adapterStatus": "available",
  "supportsNativeSkills": true,
  "placements": [
    {
      "id": "project",
      "item": "skills",
      "support": "native",
      "scope": "project",
      "defaultPath": ".team-agent/skills",
      "createIfMissing": true
    }
  ]
}

Here, .distributor/adapters/team-agent.json is relative to the directory in which distributor runs. .team-agent/skills is relative to the project root, so it resolves to /work/my-app/.team-agent/skills. createIfMissing: true lets Distributor create the target directory when needed.

Add the adapter ID to distributor.config.json if the project is already initialized:

{
  "scope": "project",
  "source": ".agents/skills",
  "harnesses": [{ "name": "team-agent", "useHarnessFolder": true }]
}

In this configuration, .agents/skills resolves to /work/my-app/.agents/skills. After adding a skill there, preview and apply the links:

mkdir -p .agents/skills/code-review
printf '%s\n' '---' 'name: code-review' 'description: Review code changes.' '---' \
  > .agents/skills/code-review/SKILL.md

distributor sync --dry-run
distributor sync

The resulting target directory is /work/my-app/.team-agent/skills/code-review, a symbolic link to /work/my-app/.agents/skills/code-review. Distributor links each top-level skill or helper directory as a unit and does not replace the harness's entire skills root.

A new project can instead create the adapter first and run distributor init or distributor init --yes. Available custom adapters are included during initialization.

Example: project and user placements with JavaScript

Use multiple placements when a harness supports both a repository-local skill directory and a user-wide directory. Create .distributor/adapters/workbench.js:

const adapter = {
  name: "workbench",
  displayName: "Workbench",
  adapterStatus: "available",
  supportsNativeSkills: true,
  defaultProjectPlacementId: "project",
  placements: [
    {
      id: "project",
      item: "skills",
      support: "native",
      scope: "project",
      defaultPath: ".workbench/skills",
      createIfMissing: true,
    },
    {
      id: "user",
      item: "skills",
      support: "native",
      scope: "user",
      defaultPath: "~/.config/workbench/skills",
      createIfMissing: true,
    },
  ],
};

export default adapter;

The two path roots are different:

  • project resolves .workbench/skills from the config directory, for example /work/my-app/.workbench/skills.
  • user expands ~ to the current user's home directory, for example /home/alex/.config/workbench/skills.
  • defaultProjectPlacementId: "project" is the automatic harness-folder placement when scope is project. It is required because this adapter declares more than one placement.

An object without targets selects automatic placement for the configured scope:

{
  "scope": "project",
  "source": ".agents/skills",
  "harnesses": [{ "name": "workbench", "useHarnessFolder": true }]
}

To link to both locations regardless of the top-level scope, select them explicitly:

{
  "scope": "project",
  "source": ".agents/skills",
  "harnesses": [
    {
      "name": "workbench",
      "useHarnessFolder": true,
      "targets": [
        { "placement": "project" },
        { "placement": "user" }
      ]
    }
  ]
}

The placement IDs in targets refer to placements[].id in the adapter; they are not filesystem paths.

Example: typed adapter with compatibility and environment overrides

TypeScript provides editor and compiler feedback through Distributor's public HarnessConfig type. This more complete adapter models a native directory, a legacy compatible directory, and a configurable user directory. Create .distributor/adapters/acme-agent.ts:

import type { HarnessConfig } from "@sunnygg/distributor";

const adapter = {
  name: "acme-agent",
  displayName: "Acme Agent",
  adapterStatus: "available",
  supportsNativeSkills: true,
  defaultProjectPlacementId: "project",
  placements: [
    {
      id: "project",
      item: "skills",
      support: "native",
      scope: "project",
      defaultPath: ".acme/skills",
      createIfMissing: true,
      notes: "Preferred repository-local directory.",
    },
    {
      id: "legacy-project",
      item: "skills",
      support: "compatibility",
      scope: "project",
      defaultPath: ".acme-agent/skills",
      createIfMissing: true,
      notes: "Directory recognized by older Acme Agent releases.",
    },
    {
      id: "user",
      item: "skills",
      support: "native",
      scope: "user",
      defaultPath: "~/.config/acme-agent/skills",
      environmentVariables: ["ACME_AGENT_SKILLS_DIR"],
      createIfMissing: true,
    },
  ],
  sources: ["https://acme-agent.example/docs/skills"],
  verifiedAt: "2026-07-22",
} satisfies HarnessConfig;

export default adapter;

Replace the example documentation URL and verification date with the real harness documentation and the date on which you checked it. The paths resolve as follows:

  • .acme/skills becomes /work/my-app/.acme/skills.
  • .acme-agent/skills becomes /work/my-app/.acme-agent/skills.
  • ~/.config/acme-agent/skills becomes, for example, /home/alex/.config/acme-agent/skills.
  • If the user placement is selected explicitly and ACME_AGENT_SKILLS_DIR=/mnt/acme-skills, it uses /mnt/acme-skills instead of its defaultPath. import also scans that location. Distributor uses the first non-empty variable listed in environmentVariables.

A project configuration can select the legacy placement and override its root:

{
  "scope": "project",
  "source": "$PROJECT_ROOT/.agents/skills",
  "harnesses": [
    {
      "name": "acme-agent",
      "useHarnessFolder": true,
      "targets": [
        {
          "placement": "legacy-project",
          "path": "$PROJECT_ROOT/.local/acme-skills"
        }
      ]
    }
  ]
}

Here, both $PROJECT_ROOT values expand to /work/my-app. The source is still /work/my-app/.agents/skills; the selected target is /work/my-app/.local/acme-skills, not the placement's default /work/my-app/.acme-agent/skills. An explicit target path takes priority over both environmentVariables and defaultPath.

Adapter field reference

| Field | Meaning | | ----- | ------- | | name | Unique lowercase kebab-case harness ID used in harnesses and --harness. It must not duplicate a built-in or custom adapter ID. | | displayName | Human-readable name shown by Distributor. | | adapterStatus | Use available for a working adapter. planned and blocked entries are catalog metadata and cannot be selected for sync. | | supportsNativeSkills | Whether the harness itself supports the Agent Skills format. | | defaultProjectPlacementId | ID of the native or compatible project placement used automatically for project scope. It may be omitted only when an available adapter has exactly one placement. | | placements | Non-empty list of directories the harness can read. Placement IDs must be unique within the adapter. | | sources | Optional non-empty list of documentation URLs used to establish the adapter metadata. | | verifiedAt | Optional ISO date (YYYY-MM-DD) when those sources and paths were verified. |

Each placement contains:

| Field | Meaning | | ----- | ------- | | id | Name used by targets[].placement, such as project, legacy-project, or user. | | item | Kind of content at this location. Use skills; import scans only placements whose item is skills. | | support | native when documented by the harness, compatibility when the harness intentionally recognizes another layout, or unverified when support has not been confirmed. Automatic placement and import use native or compatible skill placements. | | scope | project for a repository-local path or user for a per-user path. Custom sync targets are limited to these scopes. | | defaultPath | Target root used when project configuration does not supply an explicit path. Relative values resolve from the project root. ~, $HOME, and $PROJECT_ROOT are supported. | | environmentVariables | Optional ordered list of environment variables used by explicit placement resolution and import; the first non-empty value replaces defaultPath. An explicit target path still takes priority. | | createIfMissing | Whether Distributor may create missing target directories. Set it to false only when the harness must create the directory itself. | | notes | Optional explanation of requirements, versions, or compatibility behavior. |

JSON files contain the adapter object directly. JavaScript and TypeScript files must default-export it and are trusted executable code: they run when Distributor loads them. Commit and use executable adapters only when you trust their source.

New initialization metadata keeps .distributor/adapters visible to Git while ignoring local state. Existing .distributor/.gitignore files may need:

!adapters/
!adapters/**

If an adapter is not found, first confirm the command's working directory. If it fails validation, check that every required field shown above is present, that all IDs are unique, that an available multi-placement adapter names a valid project default, and that the adapter's name does not collide with a built-in adapter.

Available adapters and placements

| Harness ID | Native project behavior (useHarnessFolder: true) | Explicit selectable placements | | ---------------- | ---------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | | codex | Uses .agents/skills; global harness-folder sync uses .codex/skills | project, user (~/.codex/skills), agents-user (~/.agents/skills), admin | | claude-code | Uses .claude/skills; the default source is linked there | project (.claude/skills), user (~/.claude/skills) | | opencode | Uses .opencode/skills, but also recognizes .agents/skills and .claude/skills | project, agents-project, claude-project, user, agents-user, claude-user | | cursor | Uses .cursor/skills and recognizes .agents/skills | project, agents-project, user, agents-user | | gemini-cli | Uses .gemini/skills and recognizes .agents/skills | project, agents-project, user, agents-user | | antigravity | Uses .agents/skills; the default source is already satisfied | project, legacy-project, user | | github-copilot | Uses .github/skills and recognizes shared Agent/Claude paths | project, agents-project, claude-project, user, agents-user | | openhands | Uses .agents/skills; the default source is already satisfied | project, legacy-project, user, openhands-user | | pi | Uses .pi/skills and recognizes .agents/skills | project, agents-project, user, agents-user | | cline | Uses .cline/skills and recognizes Cline Rules and Claude paths | project, clinerules-project, claude-project, user | | goose | Uses .agents/skills; requires the Summon extension | project, goose-project, claude-project, user, claude-user | | crush | Uses .crush/skills and recognizes shared Agent/Claude/Cursor paths | project, agents-project, claude-project, cursor-project, user, agents-user, claude-user | | qwen-code | Uses .qwen/skills | project, user | | kilo-code | Uses .kilo/skills and recognizes the shared Agent path | project, agents-project, user | | roo-code | Uses .roo/skills and recognizes .agents/skills | project, agents-project, user, agents-user | | trae-agent | Uses .trae/skills | project, user |

With useHarnessFolder: false, automatic resolution uses the compatible .agents/skills placement shown in the table when one exists. Otherwise it uses the same native placement as true. An explicit target array uses exactly the placements listed in that array.

Project placements stay inside the project unless an explicit path says otherwise. Each adapter declares its user paths; inspect the table's placement IDs or the harness specification before selecting one explicitly.

If a project-local source is linked to a target outside the project, Distributor uses an absolute source link.

Ownership, conflicts, and stale targets

Distributor records ownership in <project-root>/.distributor/state.json. This is local implementation state and should not be committed. A state entry grants ownership only while the target remains a symlink with the exact raw link value Distributor recorded.

  • An existing exact expected symlink can be adopted without replacing it.
  • An unmanaged file, directory, different symlink, or user-modified managed symlink is a conflict and is never overwritten.
  • Planning inspects every requested harness before applying. One conflict makes the entire plan non-applicable, with no target or state writes.
  • Independent apply-time failures may leave other successful operations in place and recorded; the run exits 1 and remains safe to retry.
  • A managed target becomes stale when its source disappears, its harness is removed, or its placement changes. Stale targets are removed during sync after Distributor verifies that the recorded link is unchanged.
  • distributor remove removes every recorded target that is still the exact symbolic link Distributor recorded. Missing targets are cleared from state; changed links and non-links are reported and preserved. Empty target directories are left in place.

Exit codes

| Code | Meaning | | ---- | -------------------------------------------------------------------- | | 0 | Success, including no-op, warning-only, and stale-only runs | | 1 | Operational, source, state, conflict, symlink, or filesystem failure | | 2 | Invalid command usage or invalid/missing project configuration |

Initial-release scope

All sixteen harnesses in the configuration specification are available. This release intentionally has no force mode, copy or junction fallback, automatic cleanup during sync, transforms, generated artifacts, or content/frontmatter rewriting. Distributor creates direct directory symlinks for skill and helper folders, and file symlinks only for top-level helper files.