npm.io
1.0.7 • Published 9h agoCLI

@sunnygg/distributor

Licence
MIT
Version
1.0.7
Deps
7
Size
565 kB
Vulns
0
Weekly
0

Distributor

Synchronize one canonical Agent Skills tree across your agent harnesses.

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 and must have valid skill frontmatter. Other non-hidden files and directories are helper content and are preserved at the same relative paths as independent file links. The optional agents/openai.yaml (or agents/openai.yml) is emitted only to Codex targets and omitted from Claude, Cline, and every other non-Codex target.

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 reports the number of source skills, the number of active skill-to-placement references, and whether those 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": [
    "codex",
    "claude-code",
    "opencode",
    "cursor",
    "gemini-cli",
    "antigravity",
    "github-copilot",
    "openhands",
    "pi",
    "cline",
    "goose",
    "crush",
    "qwen-code",
    "kilo-code",
    "roo-code",
    "trae-agent"
  ]
}

scope defaults to project when omitted. project links skills into harness directories in the project. global still reads the source from the project, but links those skills into user-level harness directories. Harnesses that support ~/.agents/skills share that target; other harnesses use their native user skill directory.

source defaults to .agents/skills when omitted. A string harness, or an object without targets, requests automatic placement for the configured scope. The expanded form below shows explicit project and user placements plus a custom project target:

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

Configuration rules:

  • harnesses is a non-empty array. 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: [
    "codex",
    "claude-code",
    "opencode",
    "cursor",
    "gemini-cli",
    "antigravity",
    "github-copilot",
    "openhands",
    "pi",
    "cline",
    "goose",
    "crush",
    "qwen-code",
    "kilo-code",
    "roo-code",
    "trae-agent",
  ],
} 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

Distributor loads custom adapters from .distributor/adapters in the exact directory where the command is run. It reads immediate files ending in .json, .js, or .ts; other files and nested directories are ignored. The adapter's name field is its harness ID, regardless of the filename.

JSON files contain the adapter object directly. JavaScript and TypeScript files must default-export it. Import the public HarnessConfig type to check a TypeScript adapter:

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

const adapter = {
  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,
    },
  ],
} satisfies HarnessConfig;

export default adapter;

sources and verifiedAt are optional. When present, sources must be a non-empty array of URLs and verifiedAt must be an ISO date such as 2026-07-17.

An available adapter may omit defaultProjectPlacementId when it declares exactly one placement; Distributor uses that placement as the default. Adapters with multiple placements must identify their default project placement explicitly.

Adapter IDs must be unique across built-in and custom adapters. Distributor stops with an error instead of overriding either adapter when it finds a duplicate. Available custom adapters can be named in project configuration and are included in both interactive initialization and distributor init --yes.

JavaScript and TypeScript adapters are trusted executable code and run when Distributor loads them. Commit only adapters you trust. New initialization metadata keeps .distributor/adapters visible to Git while ignoring local state; existing .distributor/.gitignore files may need these rules:

!adapters/
!adapters/**

Available adapters and placements

Harness ID Automatic project behavior Explicit selectable placements
codex Uses .agents/skills; the default source is already satisfied project (.agents/skills), user (~/.agents/skills)
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

Automatic resolution checks all native and compatible project placements before choosing a fallback. 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 warns and uses an absolute source link. Moving or deleting the project will break that external target.

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 file symlinks and never directory symlinks.

Keywords