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

skillrepo

v4.22.1

Published

Pull-based CLI for agent skills — init, sync, search, add, remove your library, synced across your dev environment

Readme

skillrepo

A pull-based CLI for managing your agent-skills library from the terminal.

npx skillrepo init

Validates your access key, picks which agents to configure, wires up the MCP config, and pulls your library. Safe to re-run at any time.

Install

# One-off via npx (recommended)
npx skillrepo <command>

# Or install globally
npm install -g skillrepo
skillrepo <command>

Requires Node.js 18 or later.

Quick start

  1. Run init inside your project directory:
    npx skillrepo init
    When no key is configured, init asks for your access key. Paste it if you have one; otherwise press Enter and it opens skillrepo.dev/cli/auth in your browser, where you sign in and get a key to paste back. The page works headless too — copy the key from any browser if your terminal can't auto-launch one.
  2. Your library is now on disk. The CLI wrote skills into .claude/skills/ (for Claude Code) and .agents/skills/ (the shared path for Cursor, Windsurf, Gemini CLI, Codex CLI, Cline, VS Code + Copilot, and others). Both paths are added to .gitignore — skills are a per-developer cache, not committed.
  3. From now on, use skillrepo update to pull new versions, skillrepo add to add skills to your library, and skillrepo list to see what you have.

If you'd rather provision the key yourself, create one at skillrepo.dev/app/settings (Settings → Access Keys) and pass --key <sk_live_...> to init.

Commands

init — first-run setup

skillrepo init [--key <key>] [--url <url>] [--yes] [--force] [--agent <list>] [--global] [--json] [--no-session-sync]

Validates your access key, picks which targets to configure (interactive two-row picker by default — Claude Code at .claude/skills/ and the shared .agents/skills/ cohort path for everything else), writes the MCP config, installs the Claude Code SessionStart hook (opt-in — see session-sync below), and runs the first library sync.

Detection drives the picker's pre-checked rows. Three signal types per agent are probed:

  • Active session env vars (e.g., CLAUDECODE, CURSOR_AGENT, GEMINI_CLI, CLINE_ACTIVE) — strongest signal, indicates the agent is currently running.
  • HOME traces (e.g., ~/.claude/, ~/.cursor/, ~/.codeium/windsurf/) — installed-on-this-machine signal.
  • Project dotfiles (e.g., .claude/, .cursor/, .github/skills/) — configured-for-this-repo signal.

When any signal fires for either target, that row pre-checks. Fresh clone with no detection: both rows still pre-checked — the product's job is to set up skills, defaulting to "do nothing" because env vars didn't fire is wrong. Pick the third row ("None") to skip placement and just write the config + gitignore.

| Flag | Description | |------|-------------| | --key, -k <key> | Access key. Falls back to SKILLREPO_ACCESS_KEY env var, then interactive prompt. | | --url, -u <url> | Server URL. Defaults to https://skillrepo.dev. Use for self-hosted. | | --yes, -y | Non-interactive. Skip all confirmation prompts. Required for CI. Installs the session-sync hook by default — pass --no-session-sync to opt out. Under npx, this also auto-runs npm install -g skillrepo@<this-version> if no global install is present (so the session-sync hook can use the resulting absolute path). | | --force | Re-prompt for a new key even if ~/.claude/skillrepo/config.json is valid. | | --agent <list> | Comma-separated agent target override. Canonical tokens: claude (Claude Code), agents (every other supported vendor — Cursor, Windsurf, Gemini CLI, Codex CLI, Cline, VS Code + Copilot — sharing the .agents/skills/ cohort path), none (skip placement entirely; init still writes config + gitignore). Vendor names like cursor or claude-code are accepted as silent aliases. | | --global | Write skills to ~/.claude/skills/ (personal) instead of .claude/skills/ (project). | | --no-session-sync | Skip step 6 (SessionStart hook install). Works in both interactive and --yes modes. Use for CI scripts that bootstrap a project without ever starting a Claude Code session. | | --json | Emit a structured JSON summary on success. Requires --yes. See JSON output shape below. |

JSON output shape (init --json --yes)

{
  "action": "initialized",
  "account": { "slug": "...", "id": "...", "tier": "publisher|developer|team" },
  "config": { "action": "created|updated|unchanged" },
  "vendors": ["claudeCode", "cursor", "..."],
  "mcp": {
    "merged": [".mcp.json", ".cursor/mcp.json"],
    "skipped": ["..."],
    "failed": [{ "path": "...", "reason": "..." }]
  },
  "sessionSync": {
    "action": "installed|updated|unchanged|opted-out|declined|not-applicable|skipped|failed",
    "path": "...",
    "cohortHooks": [
      {
        "vendorKey": "cursor|gemini|codex|copilot",
        "displayName": "...",
        "path": "~/.cursor/hooks.json",
        "action": "installed|updated|unchanged|failed",
        "reason": "..."
      }
    ]
  },
  "sync": {
    "added": 0,
    "updated": 0,
    "removed": 0,
    "notModified": false,
    "fullSync": true,
    "syncedAt": "2026-05-01T00:00:00.000Z",
    "globalBoundary": {
      "total": 0,
      "counts": { "global_library": 0, "global_foreign": 0, "global_shadowed": 0 },
      "names": [],
      "skillsetDeclared": false
    },
    "changes": [
      { "kind": "added", "owner": "acme", "name": "alpha", "scope": "project", "fromVersion": null, "toVersion": "1.0.0", "contentChanged": true }
    ],
    "verifiedAt": "2026-05-01T00:00:00.000Z",
    "priorRecord": { "verifiedAt": null, "attemptedAt": null }
  }
}

sync.globalBoundary (4.13.0) appears on every successful project-scope sync and describes the personal-scope skills that also load in sessions here; it is absent when the scan did not run (--global, or a scan failure). names stays local to your machine — sync reporting to your organization carries counts only.

sync.changes, sync.verifiedAt and sync.priorRecord (4.22.0) are the sync engine's own report of the run — the same facts the session-start status line shows. changes lists one entry per skill the run changed (kind: added, updated, restored, removed, quarantined; a byte-identical rewrite on a full re-fetch is not an entry, unlike the updated counter). Each entry's contentChanged says whether the content placed differs from what the CLI's record held for the skill — not whether files moved: a restored folder re-placed with the recorded content reads false, a removal reads true. verifiedAt is the timestamp the run stamped on its record on a successful 304 or complete 200. It is null when the run reached the registry but did not persist — then incomplete: true and incompleteReason (partial-payload, invalid-skill, missing-etag, state-write-failed, no-prior-state) say why — and also null, without incomplete, when the registry was not contacted at all (throttled, servedLastVerified). kind and incompleteReason are open enums — tolerate values you do not know. priorRecord carries the record's verifiedAt / attemptedAt as they stood before the run. skillrepo update --json prints the same summary. changes, verifiedAt and priorRecord are absent from the two summaries init synthesizes without running the engine: --agent none (the sync is skipped) and a first sync that failed (sync.failureReason).

Field notes:

  • vendors is the resolved canonical-key list, NOT the raw --agent input. --agent agents produces every cohort vendor (cursor, windsurf, gemini, codex, cline, copilot); --agent none produces an empty array.
  • sessionSync.cohortHooks[] reports per-vendor outcomes for the auto-refresh hooks installed alongside the Claude Code SessionStart hook (one entry per cohort vendor with a non-null agentHook registry spec — Cursor, Gemini CLI, Codex CLI, VS Code + Copilot). reason is present only when action: "failed". Empty array when --no-session-sync was passed or no cohort vendor was selected.
  • sync.fullSync is true for first-time syncs (no prior .last-sync), false for delta syncs, and null only on synthesized-failure summaries when the network call never completed (also signals via sync.failureReason).
  • sync.failureReason: string is present only when the first sync failed but the rest of init succeeded — config is still saved; user should re-run skillrepo update.

init is idempotent: re-running with a valid existing config re-runs detection + MCP merge + first sync without re-prompting for a key. If the stored key has been revoked (401 from /auth/validate), the CLI falls back to the interactive prompt automatically; pressing Enter there opens skillrepo.dev/cli/auth to get a fresh key.

When stdin is not a TTY (CI, piped input), init skips the browser launch and just prints the URL — paste the key via the upstream pipe.

Headless / non-interactive: under --yes the picker is skipped and the pre-checked rows become the selection. With no detection signals, both default rows are pre-checked — running init --yes on a fresh clone configures both targets so CI bootstrap scripts always produce a working library. Pass --agent <target> (claude, agents, none, or a vendor name like cursor) to bypass the picker entirely.

update — sync your library

skillrepo update [--global] [--agent <list>] [--json] [--silent]

Pulls the latest state of your library from the server using a delta sync. Writes new and updated skills, removes skills that left your library, and skips skills that are unchanged. Removals apply on both the delta sync and the periodic full re-sync (the one that runs on a first sync, after a CLI upgrade, or when a new editor is detected), so a skill your team recalled or deleted stops loading everywhere rather than lingering on disk. If you had edited a removed skill locally, its folder isn't discarded — it is moved to a quarantine area (~/.claude/skillrepo/quarantine/) with a warning, so it leaves the folders your agents load while your changes are preserved. In a repo that declares a skillset, update instead reconciles that declared set (a full-set resolve, not a delta). Uses ETag caching so repeat runs return 304 Not Modified when nothing has changed.

--silent suppresses normal output and writes a single {} line to stdout. It is what the Cursor hook runs (Cursor has no user-visible hook output), and what Gemini, Codex and Copilot hooks written before v4.21.0 still run. Sync progress and warnings go to stderr. Since v4.21.0 a failure also goes to stderr as one line and the process still exits 0 and still prints {} — a hook exit never ends a session on any host (it used to propagate the error's exit code; an exit of 2 is what VS Code treats as a blocking error).

--agent-hook <gemini|codex|copilot> (v4.21.0+) is what the other cohort hooks run. Same sync as --session-hook, always exit 0, one JSON document whose systemMessage is the status line: Gemini CLI shows it at session start, Codex CLI shows it as a warning, and VS Code shows it when it runs the Copilot hook file (the Copilot CLI consumes only the agent's additionalContext, which the Copilot document carries in both hosts' shapes). A failed sync shows its ⚠ line instead of ending the hook. An unknown vendor prints a readable failure line and exits 0. The command needs skillrepo 4.21.0 or newer to be the version npx resolves; an older binary (a project-local node_modules/.bin/skillrepo, or a registry mirror that lags) rejects the flag as an unknown argument and the host shows that as a hook warning until the install catches up.

--session-hook (what the Claude Code SessionStart hook runs) prints one JSON document on every run, success or failure, and always exits 0:

{
  "systemMessage": "✓ SkillRepo · in sync · 37 skills (31 project · 6 user-level) · verified 4 min ago",
  "hookSpecificOutput": {
    "hookEventName": "SessionStart",
    "reloadSkills": true,
    "additionalContext": "[SkillRepo] 6 global skills will also load in this session: …"
  }
}

systemMessage is the status line Claude Code shows you (see session-sync below for what it says). additionalContext is what the agent reads — the global-boundary disclosure and, in a declared repo, the compliance state — unchanged from earlier versions, which printed them as plain text. reloadSkills is present only when skills were written, so the session's skill discovery picks them up before the first prompt.

Every sync also inventories the skill folders it manages. A directory the CLI did not write — a hand-authored or copied-in skill — gets a one-line warning naming the path, why it matters (agents load skills from disk whether or not they came from your library), and what to do about it (skillrepo push <path> to bring it into your library, or remove the directory). Each finding is reported once: the warning repeats only when something changes, and only interactive runs print it (session auto-sync hooks stay quiet). Warnings are local only (skill names never leave your machine), and nothing is ever deleted or modified. --json output includes the total count as unmanaged. Sync also warns — once per repo, same rules — when a skillrepo.json at the repo root is gitignored: the skillset declaration only works as a committed file, so remove it from .gitignore and commit it.

Project syncs also disclose the personal scope (the global boundary): skills under the global folders (~/.claude/skills/, ~/.agents/skills/, ~/.codeium/windsurf/skills/) load in every session run inside a project, whether or not they came through a skillset. When any exist, a one-line disclosure says how many global skills will also load — in a declared repo it also counts how many sit outside the repo's skillset and how many collide with a skillset member's name. Today the session auto-sync prints that line in Claude Code only (the other agents' background sync hooks run silently by their hook contracts — see docs/vendor-paths.md for each vendor's channel); skillrepo list shows the same line everywhere. Interactive syncs add a one-time warning per finding with the remediation, and your organization's sync reporting carries counts only (global_library, global_foreign, global_shadowed), never names.

A repository can declare a skillset in a root skillrepo.json: {"skillset": {"version": 1, "name": "<repo-identity>", "use": "owner/skillset-name"}} (optional extra: ["owner/skill"]). The CLI finds the file from any subdirectory (it walks up to the repository root), validates it, and syncs that repository against the declared skillset instead of your whole library — a declared repo only ever receives its declared skillset. Skillset-scoped sync resolves the complete set on every pass: skills added to the skillset appear, skills removed from it are deleted from the repo's skill folders, and everything lands at the repository root no matter which subdirectory you run from. Deletes are guarded — a directory the CLI did not write is never removed or overwritten; it stays put with a warning and shows up in your team's sync reporting instead. A skill the CLI wrote that you then edited is moved to the quarantine area (~/.claude/skillrepo/quarantine/) rather than deleted when it leaves the set, so it stops loading while your edits are preserved. Declared entries in extra that cannot be honored (for example when extras are not enabled for your organization) are skipped loudly with the reason, never silently. If the registry is unreachable, the repo keeps serving its last-verified skills and the sync still exits successfully with a warning (the warning escalates once the state is more than ~72 hours old). If the declaration itself cannot be honored — unknown skillset, skillsets not enabled for your organization, an invalid file — the sync fails closed with exit code 6 and nothing is written or removed. A skillrepo.json without a skillset key is plain configuration and does not change sync. See docs/skillset-declaration.md for the full file-format reference.

get — fetch a single skill

skillrepo get <@owner/name> [--global] [--agent <list>] [--json]

One-shot fetch. Does NOT mutate your library or the server — just reads GET /api/v1/skills/{owner}/{name} and writes the skill to disk. Use this to preview or pin a specific skill without adding it to your library.

list — show what's on disk and how it compares to the server

skillrepo list [--json]

list is disk-first: it walks the skills actually on disk, then annotates each against your last sync (local integrity) and the server (governance + freshness). This means an on-disk skill is never hidden — a hand-authored directory, a skill the server has withdrawn, or one you deleted all show up, alongside the ones that are healthy.

The State column shows one worst-wins signal per row, staying quiet when a row is healthy:

  • OK — on disk, matches your last sync, and the server hasn't moved on.
  • EDITED — local files were modified since the last sync; update would overwrite them.
  • BEHIND — the server has a newer version than what you synced; run skillrepo update.
  • SERVING — the server rolled a recalled version back to an earlier one; you're being served the fallback.
  • WITHDRAWN — on disk and once served to you, but the server no longer serves it. The CLI can't tell a recall from an unpublish or a skillset re-scope, so it doesn't guess — it flags that it's no longer served (remove it, or investigate).
  • MISSING — expected (in your library, or your repo's skillset) but not on disk.
  • FOREIGN — on disk in a managed directory, but not from your library.
  • NOT DELIVERED — a skillset member the CLI declined to write (edited locally, replaced, incomplete, or invalid), with the reason.
  • NOT CHECKED — the server couldn't be reached, so freshness is unknown. list never reports a stale copy as current.

list speaks the dashboard's LATEST / SERVING vocabulary (green is used only for a served head version), so healthy and recall-fallback states read identically on the CLI and the web. Where the CLI can't verify a governance cause — a skill that's simply no longer served — it says WITHDRAWN rather than asserting RECALLED; recalled appears only in a SERVING row's reason, where the server names the recalled version. Each attention row also carries a plain-English reason — what changed, why, and what to do — listed in an attention block beneath the table. Both list views render the same way (see "Same view everywhere" below).

When multiple vendors are detected, the local integrity axis is a worst-wins rollup (missing > edited > present).

Same view everywhere. A repo that declares a skillset shows its delivered set in the identical table — same columns, same colours, same attention block as the whole-library view — plus a compliance line for the declared set. list issues a non-recording freshness probe for the declared set (a full read that resolves and records nothing) so a recalled-then-restored skill reads BEHIND even inside the sync throttle window, and a recall fallback reads SERVING rather than a false green.

Offline-capable. list works with no network: the local axes are computed from disk, and the server axis degrades to NOT CHECKED rather than failing.

Read-only. list never records a delivery, writes sync state, or consumes a warning update owes you.

The State chip is coloured to match the dashboard — green for the one healthy state (OK / LATEST), amber for SERVING, red for WITHDRAWN and MISSING. Glyphs and colour are used in TTY contexts; plain ASCII tokens (OK / EDITED / BEHIND / …) with no colour appear when stdout is not a TTY or NO_COLOR is set, so piped output stays clean.

--json is a bare array of skill objects (a top-level object with a skillset block in a declared repo). Each row carries the full model — classification, local, governance, freshness, the collapsed signal, and a per-vendor placements[] array. The pre-#2806 state (worst-wins drift rollup) field is kept for backward compatibility, so existing scripts keep working.

Each row's version is the installed (on-disk) version — what your last sync wrote — while servedVersion is the version the server currently serves. They differ whenever you hold a version the server has moved off (a recall fallback reads SERVING, a stale copy reads BEHIND), and the reason line for those rows names the served version. Before 4.17.1 the top-level version reported the served version and there was no installed-version field, so a script that read version as "what the server serves" should read servedVersion instead.

search — explore the registry

skillrepo search <query> [--limit <n>] [--json] [--semantic]

Queries /api/v1/skills/search for public skills matching query. --limit caps the result count (default 20). --semantic is accepted but currently a no-op — semantic search is a planned backend feature.

add — add a skill to your library

skillrepo add <@owner/name> [--global] [--agent <list>] [--json]

POSTs to /api/v1/library, then fetches the single skill directly and writes it to disk. Requires a write-scoped access key. On 409 (already in library), re-fetches the current version so your local state is consistent.

remove — remove a skill from your library

skillrepo remove <@owner/name> [--global] [--agent <list>] [--json]

DELETEs from /api/v1/library and deletes the local directory. Requires a write-scoped access key. The local delete is immediate and does not wait for a follow-up sync.

push — upload a local skill directory to your account

skillrepo push <path> [--idempotency-key <key>] [--key <key>] [--url <url>] [--json]

Multipart POST to /api/v1/library. Walks <path>, packages SKILL.md plus every supporting file under scripts/, references/, and assets/, and uploads them as a single request. The server picks the outcome by SKILL.md frontmatter name:

  • First push of this name → creates a private skill and returns action: "created".
  • Subsequent push with changed content → releases a new version. The server classifies the bump as major (if description, allowed-tools, or compatibility changed) or minor otherwise, and returns action: "updated" with the bump kind.
  • Identical content → no-op (SHA-matched). Returns action: "unchanged".

push is the only CLI command that uploads local content; it does not write anything back to disk. Use update to pull canonical skills from the server to your local library.

Limits: total multipart body ≤ 4.5 MB, per-file path depth ≤ 5 segments, executable/archive extensions blocked (full list in src/lib/skills/constants.ts). Anti-abuse rate limit: 6/min and 100/hr on Developer; 30/min and 500/hr on Team. (push needs a write-scoped access key, which starts on the Developer plan.)

Flags:

  • --idempotency-key <key> — explicit key for safe retries. By default the CLI generates a fresh UUID per invocation and uses it for the in-process retry loop (transient 5xx/429 get up to 3 attempts with exponential backoff and jitter). Pass an explicit key to share it across separate shell invocations (CI step retries, manual reruns) and replay the cached response. Cached responses live for 24 hours.
  • --json — emit a structured object (action, bump, owner, name, version, filesUploaded).
  • --key, --url — standard auth / endpoint flags.

Exit codes: 5 (validation — bad SKILL.md, blocked path, payload too large, plan_limit), 4 (scope — read-only key), 2 (auth), 1 (network/5xx after retries).

publish — make one of your skills visible in the public catalog

skillrepo publish <@owner/name> [--key <key>] [--url <url>] [--json]

POSTs to /api/v1/library/{owner}/{name}/publish to flip the skill's visibility from private to global. Idempotent: calling publish on an already-global skill returns 200 with action: "unchanged" (the CLI prints "Already published"). Requires a write-scoped access key.

Permission model: admin+ members can always publish. Non-admin members can publish if their account-membership has canPublish: true (or the account-wide memberCanPublish default is enabled). Without either, the CLI exits 4 / scope with code: publish_not_permitted. The same canPublish capability gates unpublish and delete symmetrically.

Publish-only preconditions that surface as exit 5 / validation:

  • namespace_unset — the account's handle is still the auto-generated placeholder; choose an Author ID in Settings first.
  • analysis_pending — safety analysis hasn't completed (only fires where analysis is enabled).
  • safety_grade_too_low — the skill's safety grade is F.

Flags:

  • --json — emit { action, owner, name }. The CLI exits non-zero on error, so the presence of stdout JSON already implies success — there is no ok field.
  • --key, --url — standard auth / endpoint flags.

unpublish — remove one of your skills from the public catalog

skillrepo unpublish <@owner/name> [--key <key>] [--url <url>] [--json]

POSTs to /api/v1/library/{owner}/{name}/unpublish to flip the skill's visibility from global to private. Pair to publish: same auth, same scope, same flag surface.

Subscribers keep their copy. Accounts that already had your skill in their library retain the version they pulled — they just stop receiving future updates unless you republish. The CLI surfaces a one-line summary (e.g. "Notified 12 subscribers") so you know how many accounts the unpublish reached.

Each affected subscriber account's owner-role member(s) receive a one-time notification email, debounced 24h per (skill, account) pair so a fast unpublish/republish/unpublish cycle doesn't spam the same recipient. The publisher's own account is excluded. Already-private skills (action: "unchanged") trigger no emails.

Flags:

  • --json — emit { action, owner, name, notifiedSubscriberCount }. notifiedSubscriberCount is 0 when action === "unchanged". No ok field — exit code carries success/failure.
  • --key, --url — standard auth / endpoint flags.

session-sync — auto-sync on Claude Code session start

skillrepo session-sync enable [--global] [--json]
skillrepo session-sync disable [--global] [--json]

Installs (or removes) a Claude Code SessionStart hook that calls skillrepo update every time you open a Claude Code session — keeping your library current without you remembering to sync manually. The hook lives in .claude/settings.local.json (per-developer, gitignored by default) and runs your existing globally-installed skillrepo binary.

By default skillrepo init prompts you to install this hook. If you said no (or passed --no-session-sync), run session-sync enable later to turn it on.

Auto-refresh hooks for other agents

For Cursor, Gemini CLI, Codex CLI, and Copilot CLI, skillrepo init writes a SessionStart hook to each agent's user-scope hook config so your library stays current across your sessions without a separate command. Each hook runs npx --yes skillrepo update …, so it works without a global skillrepo install: --agent-hook <vendor> for the agents that can show you the status line (v4.21.0+), --silent for Cursor.

| Agent | Hook config path | What you see at session start | Notes | |-------|------------------|-------------------------------|-------| | Cursor | ~/.cursor/hooks.json (sessionStart event) | Nothing — Cursor has no user-visible hook output; run skillrepo list | update --silent; timeout: 60 (seconds) | | Gemini CLI | ~/.gemini/settings.json (SessionStart event) | The status line, every session (Gemini shows a hook's systemMessage at session start) | update --agent-hook gemini; matcher: "*" group filter, timeout: 60000 (milliseconds), entry named skillrepo-update | | Codex CLI | ~/.codex/hooks.json (SessionStart event) | The status line, every session — Codex styles a hook's systemMessage as a warning. Codex runs no hook until you trust it (/hooks) | update --agent-hook codex; timeout: 60 (seconds). Codex also reads [hooks] from ~/.codex/config.toml; both sources coexist — Codex merges them at runtime, so a hand-written TOML entry won't conflict with our JSON. | | VS Code + Copilot CLI | ~/.copilot/hooks/skillrepo-update.json (sessionStart event) | VS Code: the status line, every session (its local harness reads this file and shows a hook's systemMessage). Copilot CLI: nothing — it consumes only the agent's additionalContext; run skillrepo list | update --agent-hook copilot; timeout: 60 (seconds). One file, two hosts with different schemas, so the document carries the context in both shapes and always exits 0 (VS Code treats exit 2 as a blocking error handed to the model). |

skillrepo init writes these alongside the Claude Code hook for every selected vendor that publishes a SessionStart-equivalent event, and re-running init upgrades an older --silent entry in place. --no-session-sync skips ALL of them. skillrepo uninstall --global removes them surgically — other tools' entries (1Password, Snyk, your own scripts) in those config files are preserved.

Cloud-agent runners (e.g. GitHub Codespaces, Copilot's cloud agent) read only the committed default-branch content. Because skills sync to a per-developer, gitignored cache, those runners do not see the local skill library — same documented limitation as .agents/skills/ placement. The auto-refresh hooks above are per-developer; they don't run in cloud runners.

Auto-refresh hooks for Windsurf and Cline are not yet supported — those agents lack a documented SessionStart-equivalent event today. Run skillrepo update manually in those environments.

Under npx skillrepo init, the CLI offers to install itself globally. Session sync needs the binary at a stable absolute path (the npx cache path is transient and would break on the next cache eviction). Rather than skipping with a warning the way v3.1.1 did, v3.1.2 prompts during init: "SkillRepo needs a global install to enable session sync. Install skillrepo globally now? (Y/n)" — saying yes runs npm install -g skillrepo@<version> (pinned to the version you just invoked) and then installs the hook with the resulting absolute path. Under --yes the install runs without prompting; under --no-session-sync it's skipped entirely. If the install fails (permissions, network, registry), init prints actionable next-steps and continues; the rest of init still completes successfully.

skillrepo session-sync enable does not auto-install — it's an explicit, deliberate command and assumes you already have a global install. If invoked under npx without a global install present, it returns a clear "install globally first" message rather than mutating your global package set.

The hook cannot block your session. The command it runs is '<path-to-skillrepo>' update --session-hook 2>&1 || true on macOS and Linux, and node "<path-to-skillrepo.mjs>" update --session-hook 2>&1 on Windows — a form Git Bash, PowerShell 7 and Windows PowerShell all run (Claude Code uses Git Bash when it is installed and PowerShell otherwise). The --session-hook flag makes update exit 0 on every failure — network outage, revoked key, disk error, anything — and report the failure in the status line below instead. Since v4.21.0 that guarantee is process-wide: in any hook mode (--session-hook, --agent-hook, --silent), a failure anywhere in the CLI — a broken install whose modules fail to load, a bug outside the sync, an unhandled rejection — prints one ⚠ "status unavailable" line ({} under --silent) and the process still exits 0. The binary is a tiny bootstrap that installs this net before loading anything else, so only a corrupted bootstrap itself is outside it. On POSIX systems the || true shell backstop is appended as belt-and-suspenders; on Windows it's omitted (Windows PowerShell has no ||, cmd.exe no true), so the in-process guarantee is the defense there. Session starts are never blocked by sync failures, on any supported agent: Gemini CLI documents that startup is never blocked, VS Code and the Copilot CLI treat a non-zero hook exit as a non-blocking warning, Cursor's hook is fire-and-forget, and Codex runs no hook until you trust it.

Every session starts with one status line (v4.20.0+). Claude Code shows it as a system message; it is written for you, not the agent:

✓ SkillRepo · in sync · 37 skills (31 project · 6 user-level) · verified 4 min ago
✓ SkillRepo · 2 skills synced · 31 skills from acme/backend · verified just now
    + api-smoke-testing            acme           project
    ↻ secure-coding-standard       acme-security  project   1.3 → 1.4
⚠ SkillRepo · in sync · 31 skills from acme/backend · not compliant: 6 user-level skills beyond the set · verified 4 min ago · skillrepo list for details
⚠ SkillRepo · sync failed: skillrepo.dev unreachable · using 37 skills (31 project · 6 user-level) as last verified 2 h ago · retry: skillrepo update
✓ SkillRepo · 2 skills restored to this project · 37 skills · verified just now
⚠ SkillRepo · sync incomplete: delivery could not be confirmed · using 36 skills as last verified 2 h ago · retry: skillrepo update
⚠ SkillRepo · last sync attempt failed · using 31 skills from acme/backend as last verified 3 h ago · retry: skillrepo update
⚠ SkillRepo · not set up yet · run skillrepo init

The glyph answers "can I trust this session": ✓ means synced and, in a repo that declares a skillset, compliant; ⚠ means something needs attention — a failed, incomplete or stale sync, a skill that could not be written, or a non-compliant repo — and a failure always takes it. "Restored" means the sync re-placed folders that were missing on disk (a fresh clone, a deleted directory) for skills that had not changed upstream. "Sync incomplete" means the registry answered but the delivery could not be confirmed — a skill the server could not fully serve, or one the CLI refused to write (named with its count); the sync retries next session. "Last sync attempt failed" means an earlier session's sync failed and this one was inside the throttle window. "user-level" is the home-directory scope (~/.claude/skills/, what --global writes): in a library repo those skills count toward what loads; in a declared repo any user-level skill outside the set makes the repo non-compliant. "verified" is the time since the last successful registry contact — a throttled session (next paragraph) reports the previous contact's age, never "just now". Changed skills are listed under a synced line, up to 8; more collapse to counts plus skillrepo list. Earlier versions printed nothing on a quiet session; that silence gave no way to tell "nothing changed" from "the hook never ran", so the line now prints every time.

Throttled to skip redundant syncs (v4.9.0+). Because the hook fires on every editor session, the hook-triggered update (--session-hook / --silent) syncs at most once per ~15 minutes: within that window it exits immediately with no network call. This bounds redundant per-session load. The tradeoff is that a brand-new session started shortly after a recent sync may be up to ~15 minutes behind a just-published change — and the same bound applies to local repair: if you delete or corrupt a synced skill's folder on disk, the hook restores it on its next non-throttled run, up to ~15 minutes later. A bare skillrepo update you run yourself is never throttled — it always syncs immediately (repairing any local damage) and resets the window. skillrepo list is likewise never throttled: it reads the server live (a non-recording request) when reachable and degrades to local-only when it isn't, and it never resets the window. A repo that declares a skillset throttles on its own per-repo clock, so an unrelated repo's bulk sync elsewhere on the machine can no longer starve its refresh (v4.16.0+).

Flags:

  • --global — operates on ~/.claude/settings.local.json so the hook fires in every Claude Code session across all projects on your machine.
  • --json — emit structured JSON with action, path, and command fields for scripting.

uninstall — remove SkillRepo from a project

skillrepo uninstall [--dry-run] [--yes] [--global] [--json]

Surgically removes every SkillRepo artifact from the current project:

  • mcpServers.skillrepo from .mcp.json, .cursor/mcp.json, and .vscode/mcp.json (plus the matching inputs prompt in the VS Code config)
  • SKILLREPO_ACCESS_KEY=... lines from .env.local
  • The SkillRepo section of .gitignore
  • The SkillRepo SessionStart hook from .claude/settings.local.json (if present)
  • The .claude/skills/ directory

Non-SkillRepo entries in shared files are preserved. Runs offline — no server call required, so a revoked or missing access key is not a problem. Interactive by default: the command prints a full list of what will be removed and prompts for confirmation before touching anything.

With --global, also removes:

  • mcpServers.skillrepo from ~/.codeium/windsurf/mcp_config.json
  • The ~/.claude/skills/ global skill cache
  • The ~/.claude/skillrepo/ directory (stored credentials + sync cache)
  • The SkillRepo SessionStart entry from each cohort vendor's hook config — ~/.cursor/hooks.json, ~/.gemini/settings.json, ~/.codex/hooks.json, and ~/.copilot/hooks/skillrepo-update.json. Other tools' entries (1Password, Snyk, Apiiro, the user's own hooks) and unrelated top-level keys (theme, mcpServers, etc.) are preserved — only our entry, identified by its command (npx --yes skillrepo update --silent or … --agent-hook <vendor>), is filtered out.

Flags:

  • --dry-run / -n — print what would be removed and exit without touching any file.
  • --yes / -y — skip the confirmation prompt.
  • --global — also remove user-global state. By default the command leaves your credential and other projects' integrations untouched.
  • --json — emit structured JSON instead of human output. The summary includes removed[] and errors[] arrays suitable for scripting.

The command is idempotent — a second run with nothing left to remove exits 0 and reports "Nothing to remove." If any artifact fails to remove (e.g. a file is read-only), the command continues processing the others, surfaces every error at the end, and exits with code 3 (disk error).

Configuration

Credentials

The CLI stores credentials at ~/.claude/skillrepo/config.json (chmod 0600 on POSIX):

{
  "schemaVersion": 1,
  "apiKey": "sk_live_...",
  "serverUrl": "https://skillrepo.dev",
  "accountSlug": "alice",
  "accountId": "acc_...",
  "userId": "user_...",
  "writtenAt": "2026-04-15T00:00:00Z"
}

Every command except init resolves credentials in this order:

  1. --key / --url CLI flags
  2. ~/.claude/skillrepo/config.json
  3. SKILLREPO_ACCESS_KEY / SKILLREPO_URL environment variables
  4. Hard-fail with a "run skillrepo init" hint

init is different by design. Because init owns the credential lifecycle — it writes the config file in the first place, and --force / stale-key re-prompts need to decide whether to reuse cached credentials — init uses a more nuanced order. Resolved in sequence (each step only runs if the prior ones produced no key):

  1. --key / --url CLI flags
  2. SKILLREPO_ACCESS_KEY / SKILLREPO_URL environment variables (process env, not files)
  3. Existing ~/.claude/skillrepo/config.json — skipped when --force is set
  4. .env.local and .env files in the project directory — skipped when --force is set
  5. Interactive prompt for the key; server URL falls back to https://skillrepo.dev

Two scenarios worth calling out:

  • init --force with SKILLREPO_ACCESS_KEY set: uses the env var (step 2). --force only clears the cached credentials, not the runtime env.
  • init with valid existing config and no flags/env: reuses the cached credentials for a silent no-op refresh.
  • init with a config that the server now rejects (stale key): falls through to the interactive prompt automatically, without needing --force.

Environment variables

| Variable | Purpose | |----------|---------| | SKILLREPO_ACCESS_KEY | Access key for any command. Takes precedence over the config file but not CLI flags. | | SKILLREPO_URL | Server URL. Same precedence as above. | | SKILLREPO_TIMEOUT_MS | Per-request fetch timeout in milliseconds (default 30000). Set to 0 to disable. | | SKILLREPO_NO_UPDATE_CHECK | Set to any non-empty truthy value (1, yes, true) to disable the post-command npm-registry self-staleness check. The check otherwise runs at most once per 24 hours, hits registry.npmjs.org/skillrepo/latest, and prints a one-line upgrade hint to stderr if a newer version is available. Auto-disabled when CI=true. | | NO_COLOR | Set any non-empty value to disable ANSI color in CLI output. |

Update nudge

After every command that isn't --json, the CLI does a best-effort check against https://registry.npmjs.org/skillrepo/latest and prints a one-line hint on stderr when a newer version is available:

  A newer skillrepo is available: 4.3.0 → 4.5.0
  Upgrade: npm install -g skillrepo@latest

The check is cached at ~/.claude/skillrepo/.npm-version-check for 24 hours on success and 1 hour on failure, has a 2-second fetch timeout, and is fire-and-forget — every failure mode (network error, non-2xx, parse failure, read-only FS) is swallowed silently. Set SKILLREPO_NO_UPDATE_CHECK=1 to disable. CI=true auto-disables. Output is suppressed entirely when --json is on the parent command, so structured pipelines aren't disturbed.

Skill placement

Skills land at one of two project paths, depending on the agent:

| Agent | Project path | Personal (--global) path | |---|---|---| | Claude Code | .claude/skills/<name>/ | ~/.claude/skills/<name>/ | | Cursor / Windsurf / Gemini CLI / Codex CLI / Cline / GitHub Copilot | .agents/skills/<name>/ | ~/.agents/skills/<name>/ | | Windsurf (personal-scope override) | — | ~/.codeium/windsurf/skills/<name>/ |

The CLI keeps .claude/skills/ and .agents/skills/ gitignored, re-checked on every sync: an entry is added to the SkillRepo section of your project .gitignore only when no existing rule already ignores that path (a repo that ignores .claude/ gets no .claude/skills/ line) — skills are a per-developer cache, not committed content. The --agent flag overrides detection (e.g. --agent claude,agents writes both paths). See docs/vendor-paths.md for primary-source citations on each agent's read paths.

Exit codes

| Code | Meaning | |------|---------| | 0 | Success | | 1 | Network error (unreachable server, DNS, timeout, exhausted retries) | | 2 | Auth error (invalid key, revoked or suspended account, or no access key provided) | | 3 | Disk error (cannot read or write a file/directory) | | 4 | Scope error (key lacks the required registry:write scope) | | 5 | Validation error (bad flag, malformed identifier, unknown vendor, or a prompt whose input ended before it was answered) | | 6 | Unresolvable skillset declaration — the repo's root skillrepo.json declares a skillset that cannot be honored: an invalid or nested declaration, reserved @ version syntax, an unknown skillset, or skillsets not enabled for your organization. Fail-closed: nothing was written or removed. A registry that is merely unreachable is NOT this class — the sync serves the last-verified skillset and exits 0 with a warning. | | 130 | Cancelled with Ctrl-C at a prompt |

Pass --verbose to any command to print stack traces and retry attempts on failure.

Retries

The CLI automatically retries transient server failures on every idempotent API call — specifically POST /auth/validate, GET /library, GET /skills/:owner/:name, and GET /skills/search. This means the read-only commands (update, get, list, search) and the validate + sync calls inside init benefit automatically.

  • Triggered by: 429, 502, 503, 504, and raw network errors (DNS, TCP reset, timeout).
  • NOT triggered by: 4xx auth/validation errors — those never succeed on retry — nor 500, which is treated as a real-bug signal that should surface loudly rather than be masked.
  • Backoff: exponential with full jitter, 3 attempts total, base 500ms, capped at 8 seconds per sleep.
  • Write endpoints (add, remove) are single-shot to keep behavior predictable — a 503 mid-POST could mean either "body never reached the server" or "body processed, response lost", and the client cannot distinguish the two.

Self-hosted SkillRepo

The CLI works against any SkillRepo instance that implements the v1 API. Point it at your own host:

skillrepo init --url https://skillrepo.internal.company.com

Or persist it via env var in your shell profile:

export SKILLREPO_URL=https://skillrepo.internal.company.com
export SKILLREPO_ACCESS_KEY=sk_live_...

Troubleshooting

"No access key configured" — Run skillrepo init, or pass --key, or set SKILLREPO_ACCESS_KEY.

"Invalid access key format" — Keys must start with sk_live_. The CLI trims leading and trailing whitespace from keys in every path — both the interactive prompt in init and the Bearer header used by every subsequent command — so pasting from email or a browser with a trailing newline is safe.

Picker selected nothing under --yes — Phase 3 (#1236) replaced the old "no agents detected → refuse" branch with a friendlier default: when no detection signals fire, both default rows are pre-checked, so --yes configures both targets. If you want to skip placement entirely under --yes, pass --agent none (config and gitignore still happen; only the skill files are skipped).

"Rate limit exceeded — retried automatically and still getting 429" — The CLI already retried with backoff. Wait a minute and retry. Pass --verbose to see the per-attempt timings.

Windows: skill updates use a best-effort atomic rename pattern. A crash mid-update may leave a partial state; the next update run fully overwrites it.

License

Proprietary. Copyright © 2026 SkillRepo LLC. All rights reserved. Use of this CLI is governed by the SkillRepo End User License Agreement at https://skillrepo.dev/eula. See LICENSE for the full notice.