# shelf > shelf is a personal skill library for coding agents: a single-binary CLI (`shelf`) and local dashboard (`shelf ui`) that keep a user's Agent Skills in one library (`~/.shelf/library`) and lend them to projects. Borrowed skills are copied into `.agents/skills` and `.claude/skills`, tracked by content hash in `.agents/shelf.lock.json`, renewed when agents use them, and returned after going unused until their due date. What agents most often need: - Install: `npm install -g @limyuquan/shelf` (or a binary from https://github.com/limyuquan/shelf/releases), then `shelf setup` once. It installs a short `shelf` skill for agents and, in Claude Code and Codex, hooks that renew skills on use. - Read the rules: `shelf guide` prints the full guide for agents and never fails. - Every command takes `--json` and prints one line: `{"schemaVersion":1,"ok":true,"data":…}` or `{"schemaVersion":1,"ok":false,"error":{"code","message","hint"}}`. Branch on `error.code`; `error.hint` is usually the exact fix. Commands never prompt and are safe to retry. - Start of a task: `shelf status --json`. If `data.initialized` is `false`, the project doesn't use shelf; leave it alone unless the user asks. Otherwise act on `data.actions[]`, each a runnable `command` with a `reason` (replace `` placeholders). Note that `status` returns overdue loans without local edits. - A one-line `shelf: …` note at session start means something needs attention; no note means nothing to do. - Find skills: `shelf catalog --json`, `shelf search --json` (content, quote phrases), `shelf show `, `shelf suggest --json`. Borrow only what the task needs: `shelf borrow `, or `shelf borrow @` when the user names a set. - Due soon but still needed: `shelf renew --reason ""`; not needed: `shelf return `. Without hooks (harnesses other than Claude Code and Codex), run `shelf used ` after using a skill. - Keep (`shelf keep --reason ""`) only skills covering a direct dependency of the project (framework, database, platform). Kept skills load in every session forever. - Content states: `current`; `behind` → `shelf update `; `modified` → `shelf promote ` (or `shelf detach`, or `shelf update --force`); `diverged` → review with `shelf diff ` first; `missing` → `shelf sync`. - Imports: `shelf add ` and `shelf pull ` review first. Importing from a git source with `--yes` is refused for agents (`NOT_ALLOWED`) unless the user set `allowAgentImports`; show the user the review and the command. Never pass `--force` on high-severity findings without explicit approval. - Only when the user asks: `shelf init`, `adopt`, `restore`, `rename`, `duplicate`, `archive`, `set save`/`delete`, `loan-days`, `targets` changes, and editing files under `~/.shelf`. - Never edit `.agents/shelf.lock.json` by hand, and never delete skill copies by hand (deleted copies count as `missing` and come back); use `shelf return`. - Exit codes: 0 ok, 1 internal (or `lint` errors), 2 `INVALID_ARGUMENT`/`INVALID_SKILL`, 3 `NOT_INITIALIZED`, 4 `SKILL_NOT_FOUND`/`NOT_BORROWED`, 5 `SKILL_EXISTS`/`CONFLICT`, 6 `LOCAL_CHANGES`, 7 `LOAN_LIMIT`, 8 `NOT_ALLOWED`. --- # Introduction Source: https://limyuquan.github.io/shelf/docs/introduction.md What shelf is, the problems it solves, and the model behind it: one library of your own skills, lent to projects with due dates, renewed when agents use them. ## What it is shelf is a command-line tool (`shelf`) and a local dashboard (`shelf ui`) for managing [Agent Skills](https://agentskills.io): folders with a `SKILL.md` that teach a coding agent how to do something. You keep every skill you write or trust in one library, `~/.shelf/library`. A project **borrows** the skills it needs. shelf copies them into the project's skill directories (`.agents/skills` and `.claude/skills` by default), records what it copied in a lockfile, and gives each loan a **due date**. When an agent uses a borrowed skill, its due date moves out again. When a skill goes unused until its due date, shelf returns it. It is a single binary. It needs no Node, works offline, and the dashboard listens on 127.0.0.1 only. ## Why - **Copy-pasted skills drift.** The same skill lives in ten repositories at ten different versions, and nobody knows which one is current. - **Global skill folders are harness-specific and load everywhere.** A skill in `~/.claude/skills` is invisible to Codex or Cursor, and is loaded into every project whether it is relevant or not. Every loaded skill costs context at the start of every session. - **Skill registries are a supply-chain risk.** They treat your own skills as second-class, and installing from them puts someone else's instructions in front of your agent. shelf makes your own library the source of truth and the trust boundary. Agents borrow only from it, and anything that comes from outside is audited and needs your explicit `--yes`. ## The model in one picture ``` ~/.shelf/library/ your project pdf-tools/ ── borrow ──────────▶ .agents/skills/pdf-tools/ api-design/ (one revision, .claude/skills/pdf-tools/ git-hygiene/ until a due date) .agents/shelf.lock.json ▲ │ └────────────── promote ◀──── edits ───────┘ ─────────────── update / propagate ───────▶ ``` | Thing | Where it lives | What it is | |---|---|---| | Library | `~/.shelf/library//` | The editable copy of each skill. Edit it with any editor. | | Revision | `~/.shelf/objects//` | An immutable snapshot of a skill, named by its content hash. Every change is a new revision. | | Loan | `~/.shelf/shelf.db` | A project borrowing one revision of a skill until a due date. | | Project copy | `///` | The files the harness reads, one copy per target directory. | | Lockfile | `/.agents/shelf.lock.json` | Which skills shelf manages in the project, at which revision. No timestamps. | Three rules follow from this model: 1. **Using a skill renews it.** Harness hooks see a borrowed skill being used and move its due date to the skill's loan length from now (30 days by default). 2. **Unused skills are returned.** An overdue loan is returned at the next `shelf status`, `shelf sync`, `shelf sweep` or session start, unless the project copy has local edits, which shelf never deletes. 3. **Changes move only when you say so.** Edits flow from a project to the library with `shelf promote`, and from the library to projects with `shelf update` or `shelf propagate` (or automatically for loans borrowed with `--follow`). [Concepts](https://limyuquan.github.io/shelf/docs/concepts.md) defines every term in detail. ## Who it is for shelf is for a developer who uses coding agents across several projects and has, or wants, a personal set of skills. It is single-user and per machine: the library and the loan database live in your home directory, and the lockfile carries what a project needs to other clones. It is equally meant to be operated by agents. Every command has `--json`, never prompts and is safe to retry. The bundled `shelf` skill tells agents that shelf exists, and `shelf guide` gives them the full rules. See [Agents](https://limyuquan.github.io/shelf/docs/agents.md). ## Next - [Installation](https://limyuquan.github.io/shelf/docs/installation.md) - [Quickstart](https://limyuquan.github.io/shelf/docs/quickstart.md): five minutes from install to a borrowed skill - [Concepts](https://limyuquan.github.io/shelf/docs/concepts.md) --- # Installation Source: https://limyuquan.github.io/shelf/docs/installation.md Install shelf from npm or as a standalone binary, run `shelf setup` once, and learn how to upgrade and uninstall it. shelf is one self-contained binary for macOS, Linux and Windows. It does not need Node, Bun or a network connection to run. ## With npm ```sh npm install -g @limyuquan/shelf shelf setup ``` The npm package is a small launcher plus one platform package per OS and CPU, listed as optional dependencies, so npm downloads only the binary for your machine. There are no install scripts. Node 18 or later runs the launcher. If npm was told to skip optional dependencies (`--no-optional`, `--omit=optional`), the launcher can't find a binary and says so: ``` shelf: no prebuilt binary for linux-x64 (@limyuquan/shelf-linux-x64 is not installed). Reinstall without --no-optional / --omit=optional, or download a binary from the GitHub release. ``` ## Binaries Download an archive for your platform from the [releases page](https://github.com/limyuquan/shelf/releases): | Platform | Archive | |---|---| | macOS, Apple silicon | `shelf-darwin-arm64.tar.gz` | | macOS, Intel | `shelf-darwin-x64.tar.gz` | | Linux, x64 | `shelf-linux-x64.tar.gz` | | Linux, arm64 | `shelf-linux-arm64.tar.gz` | | Windows, x64 | `shelf-win32-x64.zip` (contains `shelf.exe`) | Each release also has a `SHA256SUMS` file. Check your download against it, then put the binary on your `PATH`: ```sh sha256sum --check --ignore-missing SHA256SUMS # macOS: shasum -a 256 --check --ignore-missing SHA256SUMS tar -xzf shelf-linux-x64.tar.gz mv shelf ~/.local/bin/ shelf --version ``` ### macOS quarantine The binaries are not code-signed yet. macOS quarantines files downloaded with a browser and refuses to run them. Clear the flag once: ```sh xattr -d com.apple.quarantine shelf ``` Installs through npm, or downloads with `curl`, are not affected. ## From source shelf is built with [Bun](https://bun.com) (the version is pinned in `package.json`, currently 1.4.2): ```sh git clone https://github.com/limyuquan/shelf.git cd shelf bun install bun run build # produces dist/shelf ``` `bun run build:all` cross-compiles every platform into `dist/`. See [Contributing](https://limyuquan.github.io/shelf/docs/contributing.md) for the development workflow. ## Set up Run `shelf setup` once after installing, and again after every upgrade: ```console $ shelf setup Shelf home: /home/me/.shelf Library: /home/me/.shelf/library Installed the shelf skill at: /home/me/.agents/skills/shelf/SKILL.md /home/me/.claude/skills/shelf/SKILL.md Hooks (renew skills when used, report loans needing attention): Claude Code: hooks installed (/home/me/.claude/settings.json) Codex: hooks installed (/home/me/.codex/hooks.json) Codex runs new hooks only after you trust them: open `/hooks` in Codex once. ``` It does three things, all safe to repeat: 1. Creates the shelf home (`~/.shelf`, or `$SHELF_HOME`) with `library/`, `objects/` and a `config.json` holding every default. 2. Installs the bundled `shelf` skill in your user-level skill directories, so agents in every project know shelf exists. It costs about 66 tokens of context per session. 3. Installs [hooks](https://limyuquan.github.io/shelf/docs/hooks.md) in Claude Code and Codex, if they are installed, so loans renew when agents use skills. Codex runs new hooks only after you trust them: open `/hooks` in Codex once. To skip the hooks, run `shelf setup --no-hooks`. See [`shelf setup`](cli/setup.md) for details. Then check everything with: ```sh shelf doctor ``` ## Upgrading Install the new version the same way you installed the old one (`npm install -g @limyuquan/shelf`, or replace the binary), then run: ```sh shelf setup ``` This refreshes the bundled skill and points the hooks at the new binary. The hooks call the binary by its absolute path, so moving or replacing the binary at a different path leaves them pointing at the old one; `shelf doctor` reports this and `shelf doctor --fix` repairs it. Your library, revisions, loans and config are kept across upgrades. The database migrates itself the first time the new version opens it. ## Uninstalling 1. Remove the hooks while shelf is still installed: ```sh shelf setup --no-hooks ``` This removes only shelf's entries from `~/.claude/settings.json` and `~/.codex/hooks.json`. 2. In each project, decide what happens to borrowed skills. `shelf detach ` keeps the files as ordinary project files; `shelf return ` removes them. Then delete `.agents/shelf.lock.json`. `shelf projects` lists every project. 3. Delete the bundled skill: `~/.agents/skills/shelf/` and `~/.claude/skills/shelf/` (and the same folder under any other harness that `shelf setup` listed). 4. Back up your library if you want to keep your skills (`~/.shelf/library` holds plain skill folders), then delete `~/.shelf`. 5. Remove the binary: `npm uninstall -g @limyuquan/shelf`, or delete the file. See [Files](https://limyuquan.github.io/shelf/docs/files.md) for everything shelf writes. --- # Quickstart Source: https://limyuquan.github.io/shelf/docs/quickstart.md Five minutes from a fresh install to a skill borrowed into a project: set up, create or adopt a skill, borrow it, check the project's status and open the dashboard. This page assumes shelf is [installed](https://limyuquan.github.io/shelf/docs/installation.md). The examples use a project called `storefront`; use any project of yours. ## 1. Set up ```console $ shelf setup Shelf home: /home/me/.shelf Library: /home/me/.shelf/library Installed the shelf skill at: /home/me/.agents/skills/shelf/SKILL.md /home/me/.claude/skills/shelf/SKILL.md Hooks (renew skills when used, report loans needing attention): Claude Code: hooks installed (/home/me/.claude/settings.json) Codex: hooks installed (/home/me/.codex/hooks.json) Codex runs new hooks only after you trust them: open `/hooks` in Codex once. ``` ## 2. Put a skill in the library Create a new skill: ```console $ shelf new pdf-tools -d "Extract text, tables and form fields from PDFs, and fill or merge PDF files. Use when a task involves reading or producing PDFs." Created pdf-tools at /home/me/.shelf/library/pdf-tools Edit its files with any editor; shelf records each change as a new revision. ``` Open `~/.shelf/library/pdf-tools/SKILL.md` in your editor and write the instructions. shelf notices the change the next time any command reads the library and records it as a new revision. Check the format with: ```console $ shelf lint pdf-tools pdf-tools ~32 description + ~17 body tokens ok ``` Already have skills copied into your projects? Adopt them instead of starting over: ```sh shelf scan ~/code # find every copy, grouped by name and version shelf adopt ~/code/storefront/.claude/skills/review # import one into the library ``` See [Migrating](https://limyuquan.github.io/shelf/docs/migrating.md) for drifted copies and the dashboard's **Find existing skills**. ## 3. Borrow it into a project ```console $ cd ~/code/storefront $ shelf init Initialized shelf in /home/me/code/storefront $ shelf borrow pdf-tools Borrowed pdf-tools (due 2026-11-06) → .agents/skills, .claude/skills ``` `shelf init` creates `.agents/shelf.lock.json`. `shelf borrow` copies the skill into both default skill directories, so Claude Code (`.claude/skills`) and Codex, Cursor, Gemini CLI, Copilot and the rest (`.agents/skills`) all find it. Commit the lockfile. ## 4. Check the project ```console $ shelf status storefront /home/me/code/storefront SKILL CONTENT DUE USED POLICY REVISION pdf-tools current 2026-11-06 30d left never pinned ddad27dd34 ``` Each loan has a content state (`current` here), a due date, its last use, a policy and the revision it holds. When something needs doing, `shelf status` ends with **Next steps**: exact commands with the reason for each. From now on you rarely need to do anything. When an agent uses `pdf-tools`, the hooks move its due date to 30 days from that day. If nobody uses it for 30 days, the next session start returns it. If a session starts while something needs attention, the agent sees one line like this one: ``` shelf: due soon unless used: git-hygiene (4d) — `shelf renew ` to keep, `shelf return ` if unneeded. Details: `shelf status`. ``` ## 5. Open the dashboard ```console $ shelf ui shelf dashboard: http://127.0.0.1:4222/?token= Ctrl-C to stop. ``` The browser opens on **Attention**: every loan across your projects that needs you. Projects, the library editor, revisions, activity and insights are one click away. See [Dashboard](https://limyuquan.github.io/shelf/docs/dashboard.md). ## Next - [Concepts](https://limyuquan.github.io/shelf/docs/concepts.md): content states, due dates, keep, sets and the lockfile - [Borrowing](https://limyuquan.github.io/shelf/docs/borrowing.md): renew, return, due dates and `--follow` - [Keeping skills current](https://limyuquan.github.io/shelf/docs/keeping-skills-current.md): edit once, update everywhere - [Agents](https://limyuquan.github.io/shelf/docs/agents.md): what your agents do with shelf on their own --- # Concepts Source: https://limyuquan.github.io/shelf/docs/concepts.md Every term shelf uses, defined precisely: library, skill, revision, project, loan, target, harness, due date, loan length, renew on use, keep, policy, mode, content states, the lockfile, sets and actors. ## Library and skills The **library** is the directory `~/.shelf/library` (or `$SHELF_HOME/library`). Each subdirectory with a `SKILL.md` is a **skill**, in the [Agent Skills](https://agentskills.io/specification) format: YAML frontmatter with a `name` that matches the directory and a `description`, followed by instructions, plus any reference files or scripts. You edit the library with any tool. There is no daemon: every shelf command that reads skills first reconciles the library with the database, recording a new revision for anything that changed. A library directory that isn't a valid skill is skipped with a warning (see `shelf doctor`). Skill names are 1 to 64 characters of lowercase letters, digits and single hyphens, with no leading or trailing hyphen: `pdf-tools`, `react-best-practices`. ## Revisions A **revision** is an immutable snapshot of a skill directory, named by its content hash: SHA-256 over every file's relative path and the SHA-256 of its bytes, written `sha256:9f86d0…`. Commands show the first 10 hex characters (`ddad27dd34`) and accept any unique prefix of at least 6. Snapshots live in `~/.shelf/objects//` and are never deleted. Each revision records its parent and its source: | Source | Recorded when | |---|---| | `library` | The library copy was edited (or created with `shelf new`, or restored). | | `promote` | A project's edits were published with `shelf promote`. | | `import` | `shelf add` imported the skill, or `shelf pull` updated it. | | `adopt` | `shelf adopt --unedited` recorded an older copy found in a project. | The library's **latest** revision (its head) is what new loans get. `shelf log ` lists every revision and which projects hold it; `shelf restore` makes an earlier one the latest again. ## Projects A **project** is a directory that uses shelf, marked by `.agents/shelf.lock.json`. shelf finds the project root by walking up from the working directory: the nearest directory with a lockfile, else the nearest git root, else the working directory itself. Run commands from anywhere inside the project. `shelf init` registers a project. A clone of a project that already has a lockfile is recognised and registered the first time any shelf command runs in it, because the lockfile carries the project's id. A moved directory is followed the same way. The project's name is its directory name. ## Loans A **loan** is a project borrowing one revision of one skill until a **due date**. Loans live in the database (`~/.shelf/shelf.db`) on your machine. Each loan has: | Field | Meaning | |---|---| | revision | The revision the project holds (its base). | | targets | The directories the skill is copied into. | | due date | When the loan expires unless renewed. | | last used | The last time an agent was seen using the skill in this project. | | kept | Kept loans never come due. | | policy | `pinned` or `follow`. | | mode | `copy` or `link`. | **Borrow** creates a loan and writes the copies. **Return** removes the copies and closes the loan. **Detach** closes the loan but leaves the files, which then belong to the project. ## Targets and harnesses A **harness** is a coding agent that reads skills: Claude Code, Codex, Cursor and so on. A **target** is a project-relative directory that skills are copied into, such as `.claude/skills`. The default targets are `.agents/skills` (the cross-harness convention, read by Codex, Cursor, Gemini CLI, Copilot, OpenCode, Amp, Goose, Cline and others) and `.claude/skills` (Claude Code). A project can add directories for harnesses that read neither, such as Kiro, with `shelf targets --add kiro`. See [Harnesses](https://limyuquan.github.io/shelf/docs/harnesses.md). ## Mode: copy or link | Mode | What is written | |---|---| | `copy` (default) | A full copy of the skill in every target. | | `link` | One real copy in the first target; the others are relative symlinks to it (junctions on Windows). | Copies are the default because symlinked skills are unreliable in some harnesses and across the WSL/Windows boundary. Choose link mode per loan with `shelf borrow --link`, or for every new loan with `"mode": "link"` in the [config](https://limyuquan.github.io/shelf/docs/configuration.md). Hashes follow symlinks, so content states work the same in both modes. ## Due dates and loan length Every loan that isn't kept has a due date. A skill's **loan length** decides how far out it is set: - the skill's own loan length, set with `shelf loan-days ` (a setting of your library on this machine), else - `loanDays` from the config (30 by default), - never more than `maxLoanDays` (90 by default). | Event | New due date | |---|---| | `shelf borrow` | Now plus `--days`, or the loan length. | | An agent uses the skill | Now plus the loan length, if that is later than the current due date. | | `shelf renew` | Now plus `--days`, or the loan length, if that is later than the current due date. | | `shelf due +14d` | The current due date shifted by the amount. | | `shelf due 2026-12-01` | The end of that day, UTC. | No command sets a due date more than `maxLoanDays` from now; trying fails with `LOAN_LIMIT`. ## Due states | State | Meaning | |---|---| | `active` | More than `dueSoonDays` (7 by default) days left, or kept. | | `due-soon` | Due within `dueSoonDays` days. With hooks installed, this means the skill has gone unused for a while. | | `overdue` | The due date has passed. | ## Renew on use Loans renew when they are used, so a due date really means "unused for a loan period". [Hooks](https://limyuquan.github.io/shelf/docs/hooks.md) installed by `shelf setup` recognise a borrowed skill being used in Claude Code and Codex (the Skill tool naming it, a tool reading a file inside its copy, or a prompt invoking `/name`) and record the use. In harnesses without hooks, agents run `shelf used ` after using a skill. A use is written at most once an hour per loan, and appears in the activity log at most once a day. ## Expiry An overdue loan is returned automatically by the next `shelf status`, `shelf sync` or `shelf sweep` in its project, or the next session start (the session-start hook runs a sync). The copies are deleted and the loan is closed. shelf never deletes local edits. An overdue loan whose copy is `modified` or `diverged` stays, and the next steps tell you to promote or detach it. ## Keep A **kept** loan never comes due. Its due state is always `active`, nothing expires it, and uses are still recorded. Keep a skill with `shelf keep ` or borrow it with `shelf borrow --keep`; stop with `shelf keep --off`. A loan that stops being kept gets at least a fresh loan period. Keeping is a project decision, so it is written to the lockfile (`"keep": true`) and every clone keeps the same skills. Kept skills load into every session forever, so keep only what the project is built on: Convex skills in a Convex app, the API design skill in an API service. Agents follow the same rule. ## Policy: pinned or follow | Policy | When the library has a newer revision | |---|---| | `pinned` (default) | The loan is `behind` until someone runs `shelf update` (or `shelf propagate` from the library side). | | `follow` | `shelf sync`, `shelf sweep` and the session-start hook update it automatically, as long as the copy has no local edits. | Choose `follow` with `shelf borrow --follow`. ## Content states Each loan's **content state** compares three hashes: the revision the project borrowed (base), the library's latest revision (head) and each copy on disk (working). | State | Meaning | What to do | |---|---|---| | `current` | Every copy matches the borrowed revision, which is the latest. | Nothing. | | `behind` | The copies are unedited, but the library has a newer revision. | `shelf update ` | | `modified` | A copy was edited in the project; the library hasn't changed. | `shelf promote ` to publish, `shelf detach ` to keep the edits unmanaged, or `shelf update --force` to discard them. | | `diverged` | A copy was edited, and the library has changed too. | Review with `shelf diff`, then `shelf promote --force` or `shelf update --force`. | | `missing` | Some copies were deleted and the remaining ones are unedited. | `shelf sync` restores them. | Edits take precedence over missing copies: a loan with one edited copy and one deleted copy is `modified`, so restoring can never overwrite edits. Every operation that would discard edits (`return`, `update`, `promote` over a newer library revision, expiry, removing a target) refuses unless forced. ## The lockfile `.agents/shelf.lock.json` records which skills shelf manages in the project, at which revision, in which directories, and whether they are kept. It has no timestamps, so renewing a loan never changes it and committing it causes no merge churn. Commit it; never edit it by hand. See [Lockfile](https://limyuquan.github.io/shelf/docs/lockfile.md). ## Sets A **set** is a named group of library skills, such as `frontend` for `react-best-practices`, `playwright-testing` and `accessibility-audit`. `shelf borrow @frontend` borrows every skill in it. Each skill still gets its own loan, due date and lockfile entry; nothing about the set reaches the project. Sets live in your database, per machine. See [Sets](https://limyuquan.github.io/shelf/docs/sets.md). ## Actors and the activity log Every change is recorded in the activity log with its **actor**: who did it. | Actor | When | |---|---| | `user` | You, at a terminal. | | `user:dashboard` | You, in `shelf ui`. | | `agent:claude-code`, `agent:codex` | Commands run inside those harnesses, and their hooks. | | `agent:` | Other harnesses that set `AI_AGENT`. | | anything | Set with `--actor` or `SHELF_ACTOR`. | | `unknown` | Not a terminal and no harness detected (for example a cron job). | The dashboard's Activity page shows the log as a timeline. Agents are restricted in one place: they may not import skills from remote sources unless you allow it. See [Environment](https://limyuquan.github.io/shelf/docs/environment.md) for how the actor is detected and [Importing skills](https://limyuquan.github.io/shelf/docs/importing-skills.md) for the rule. --- # Borrowing Source: https://limyuquan.github.io/shelf/docs/borrowing.md How to borrow skills into a project and manage the loans afterwards: renewing, recording uses, moving due dates, keeping, returning, detaching and following library updates. All commands on this page act on the project that contains the working directory. Run `shelf init` in a project once before borrowing into it. ## Find a skill ```console $ shelf catalog NAME TOKENS DESCRIPTION accessibility-audit ~122 Audit interfaces for WCAG 2.2 issues: contrast, focus order, labels a… api-design ~127 Design consistent HTTP APIs: resource naming, error envelopes, pagina… pdf-tools ~116 Extract text, tables and form fields from PDFs, and fill or merge PDF… playwright-testing ~143 Write reliable end-to-end tests with Playwright: locators, fixtures a… react-best-practices ~195 Component structure, hooks rules and rendering performance for React … ``` - `shelf catalog ` filters by name and description (every term must match). - `shelf search ` looks inside skills too: SKILL.md bodies and reference files, with the matching lines. - `shelf suggest` lists library skills that match the project's dependencies and files. - `shelf show ` prints the whole skill. The token column is the size of the whole SKILL.md (characters / 4). Only a skill's name and description load at every session start; see [Context budget](https://limyuquan.github.io/shelf/docs/context-budget.md). ## Borrow ```console $ shelf borrow pdf-tools api-design Borrowed pdf-tools (due 2026-11-06) → .agents/skills, .claude/skills Borrowed api-design (due 2026-11-06) → .agents/skills, .claude/skills ``` Each skill is copied from its latest revision into every target directory, the loan is recorded with a due date, and the lockfile is rewritten. Borrowing a skill that is already borrowed changes nothing and says so: ```console $ shelf borrow pdf-tools pdf-tools is already borrowed (due 2026-11-06) ``` | Option | Effect | |---|---| | `--days N` | Loan length in days instead of the skill's loan length. At most `maxLoanDays`. | | `--keep` | The loan never expires (also applies to skills already borrowed). | | `--follow` | `shelf sync` applies library updates to this loan automatically. | | `--link` | One copy in the first target; the other targets are symlinks to it. | | `@` | Borrow every skill in a set: `shelf borrow @frontend`. | shelf refuses to overwrite a directory it doesn't manage. If `.claude/skills/pdf-tools` already exists with other content, `borrow` fails with `CONFLICT`; adopt the copy with `shelf adopt` instead (see [Migrating](https://limyuquan.github.io/shelf/docs/migrating.md)). A leftover copy that is byte-identical to the revision being borrowed is taken over. ## Due dates New loans are due after the skill's loan length: 30 days unless you changed `loanDays` in the config or set a length for the skill with `shelf loan-days`. From then on: - **Using the skill renews it.** With the [hooks](https://limyuquan.github.io/shelf/docs/hooks.md) installed, nothing else is needed: a loan only comes due after going unused for its loan length. - **Due soon** means within 7 days (`dueSoonDays`). `shelf status` suggests renewing or returning, and the session-start note tells the agent. - **Overdue** loans are returned by the next `shelf status`, `shelf sync`, `shelf sweep` or session start, unless the copy has local edits. ```console $ shelf status storefront /home/me/code/storefront SKILL CONTENT DUE USED POLICY REVISION accessibility-audit current 2026-10-19 12d left 18d ago pinned 78f143d800 git-hygiene current 2026-10-13 6d left never pinned 098f8b659c playwright-testing current 2026-11-05 29d left 1d ago pinned a6bb6debdf react-best-practices current 2026-11-06 30d left today pinned 2bf910a993 Next steps: shelf renew git-hygiene --reason "" git-hygiene has gone unused and is due in 6 day(s). Renew it if the project still needs it, otherwise `shelf return git-hygiene` ``` ## Record a use Harnesses without hooks can't tell shelf that a skill was used. Record it yourself (or have the agent do it): ```console $ shelf used pdf-tools pdf-tools: in use, due 2026-11-06 ``` `shelf used` moves the due date to the loan length from now, exactly like a hook. Uses within an hour of the last one are not written again. ## Renew ```console $ shelf renew api-design --reason "still designing the orders API" api-design is now due 2026-11-06 ``` `renew` sets the due date to `--days` or the skill's loan length from today, like a use does, unless the loan is already due later. The reason goes into the activity log. The result can't be more than `maxLoanDays` from today: ```console $ shelf renew api-design --days 200 error: Due date 2027-06-24T05:34:17.582Z is beyond the 90-day loan limit hint: The latest allowed due date is 2027-01-05 ``` ## Move a due date `shelf due` moves a due date either way: ```console $ shelf due api-design +14d api-design is now due 2026-12-20 $ shelf due api-design 2026-12-01 api-design is now due 2026-12-01 ``` Accepted forms: `+Nd`, `-Nd`, `+Nw`, `-Nw` (relative to the current due date) and `YYYY-MM-DD` (the end of that day, UTC). A negative shift moves it earlier: `shelf due api-design -7d`. A date in the past makes the loan overdue: the next `shelf status` or `shelf sync` returns it. ## Keep ```console $ shelf keep api-design --reason "the project is an HTTP API" api-design is now kept; it never expires ``` A kept loan never comes due. `shelf status` shows `kept` in the due column. Keep only skills the project is built on, because kept skills load into every session forever. The decision is written to the lockfile, so every clone keeps the same skills. ```console $ shelf keep api-design --off Stopped keeping api-design; due 2026-12-01 unless used ``` When a loan stops being kept, its due date becomes at least one loan period from now. ## Return ```console $ shelf return pdf-tools Returned pdf-tools ``` `return` deletes the project's copies and closes the loan. If a copy has local edits, it refuses: ```console $ shelf return pdf-tools error: The project copy of "pdf-tools" has local edits hint: Keep them with `shelf promote pdf-tools` or `shelf detach pdf-tools`, or discard them with --force ``` Never delete a skill copy by hand to return it: shelf sees deleted copies as `missing` and restores them. ## Detach ```console $ shelf detach pdf-tools Detached pdf-tools; its files now belong to the project ``` `detach` closes the loan but leaves the files where they are. shelf stops tracking them, and the lockfile entry is removed. Use it to keep a project-specific variant of a skill. ## Follow library updates A loan borrowed with `--follow` takes new library revisions automatically: ```console $ shelf borrow api-design pdf-tools --follow Borrowed api-design (due 2026-11-06) → .agents/skills, .claude/skills Borrowed pdf-tools (due 2026-11-06) → .agents/skills, .claude/skills $ shelf sync # later, after both skills changed in the library Updated: api-design, pdf-tools ``` `shelf sync`, `shelf sweep` and the session-start hook update `follow` loans that are `behind`. Copies with local edits are never updated automatically. `shelf status` shows the policy in the `POLICY` column. ## Sync ```console $ shelf sync Restored: git-hygiene ``` `shelf sync` reconciles the project with its loans: it returns overdue loans without local edits, restores missing copies, and updates `follow` loans. The session-start hook runs the same sync. It prints `Everything is in sync.` when there is nothing to do. ## Related - [Keeping skills current](https://limyuquan.github.io/shelf/docs/keeping-skills-current.md): update, promote, propagate - [Sets](https://limyuquan.github.io/shelf/docs/sets.md) - [`shelf borrow`](cli/borrow.md), [`shelf renew`](cli/renew.md), [`shelf due`](cli/due.md), [`shelf keep`](cli/keep.md), [`shelf return`](cli/return.md) --- # Keeping skills current Source: https://limyuquan.github.io/shelf/docs/keeping-skills-current.md How changes move between the library and projects: updating loans to the library's latest revision, promoting a project's edits, propagating to every borrower, comparing versions, restoring old revisions, and resolving local edits. Changes never move on their own (except for loans borrowed with `--follow`). The library and each project keep their version until you run one of the commands below. ``` library ── update (in a project) ─────────▶ one project library ── propagate (from anywhere) ─────▶ every borrowing project project ── promote ───────────────────────▶ library ``` ## Change a skill in the library Edit `~/.shelf/library//` with any editor, or use the dashboard's skill editor. The next shelf command records the change as a new revision. Projects that borrow the skill become `behind`: ```console $ shelf status mobile-app /home/me/code/mobile-app SKILL CONTENT DUE USED POLICY REVISION react-best-practices behind 2026-11-04 28d left 2d ago pinned 0ee0a9e94c release-notes current 2026-10-22 15d left never pinned 0b01b7e535 Next steps: shelf update react-best-practices The library has a newer revision of react-best-practices ``` ## Update a project Inside the project: ```console $ shelf update api-design api-design: updated to e09cc9a0ed ``` With no names, `shelf update` updates every loan and skips the ones with local edits instead of failing: ```console $ shelf update api-design: already at e09cc9a0ed pdf-tools: updated to ddad27dd34 ``` Naming a skill whose copy has local edits fails with `LOCAL_CHANGES`; add `--force` to discard the edits and take the library's revision. ## Propagate to every project From anywhere, push the library's latest revision to every project that borrows the skill: ```console $ shelf propagate pdf-tools --dry-run pdf-tools → dca5f929e7 (dry run) billing-api: would update storefront: would update $ shelf propagate pdf-tools pdf-tools → dca5f929e7 billing-api: updated storefront: updated ``` Each project is reported as `updated`, `already current`, `skipped (local edits)` or `skipped (directory missing)`. Copies with local edits are never overwritten. `--project billing-api,storefront` limits it to some projects (by name, path or id). ## Promote a project's edits When an agent improves a borrowed skill inside a project, the loan becomes `modified`: ```console $ shelf status storefront /home/me/code/storefront SKILL CONTENT DUE USED POLICY REVISION api-design current 2026-12-08 63d left never pinned e09cc9a0ed pdf-tools modified 2026-11-06 30d left today pinned ddad27dd34 Next steps: shelf promote pdf-tools pdf-tools has local edits. Promote them to the library, keep them unmanaged with `shelf detach pdf-tools`, or discard them with `shelf update pdf-tools --force` ``` Review the edits, then publish them: ```console $ shelf diff pdf-tools --- a/SKILL.md +++ b/SKILL.md @@ -5,4 +5,6 @@ # pdf-tools Describe when and how an agent should apply this skill. + +- Prefer pdftotext for scanned files $ shelf promote pdf-tools Promoted pdf-tools: library ddad27dd34 → d4deea4b55 ``` The edited copy becomes the library's latest revision (source `promote`), and every copy in this project is rewritten from it. Add `--propagate` to update every other borrowing project in the same step: ```console $ shelf promote api-design --propagate Promoted api-design: library e09cc9a0ed → d74c95b92c api-design → d74c95b92c billing-api: updated storefront: already current ``` `promote` refuses when: | Situation | Error | Way out | |---|---|---| | The copy has no edits (`current` or `behind`) | `INVALID_ARGUMENT` | Nothing to promote. | | Copies are missing | `CONFLICT` | `shelf sync` first. | | The library changed since the project borrowed (`diverged`) | `CONFLICT` | Review, then `--force` to replace the library's newer revision. | | The copies in different targets were edited differently | `CONFLICT` | Make them identical, then promote. | | The edited SKILL.md is not a valid skill | `INVALID_SKILL` | Fix the frontmatter. | ## Local edits: your three choices A `modified` loan always has three ways forward: | Command | Result | |---|---| | `shelf promote ` | The edits go to the library, for every project. | | `shelf detach ` | The edits stay in this project only, as unmanaged files. shelf stops tracking the skill here. | | `shelf update --force` | The edits are discarded and the copy gets the library's latest revision. | A `diverged` loan was edited here and in the library. Compare both sides before choosing: ```sh shelf diff # this project's edits (borrowed → project) shelf diff --from borrowed --to library # what changed in the library ``` Then keep this project's version with `shelf promote --force`, or take the library's with `shelf update --force`. To combine both, edit the library copy by hand, then `shelf update --force` in the project. ## Compare versions `shelf diff ` compares two versions of a skill. Each side is one of: | Side | Meaning | |---|---| | `borrowed` | The revision this project borrowed. | | `library` or `latest` | The library's latest revision. | | `project` | This project's copy on disk (the edited one, if any). | | a revision | A hash or unique prefix of at least 6 characters (see `shelf log`). | Without `--from` and `--to`, inside a project that borrows the skill, it shows the local edits if the copy is edited, otherwise what the library changed since the project borrowed. Outside a project, it shows the latest library change. ## Revision history ```console $ shelf log pdf-tools REVISION DATE SOURCE BORROWED BY d4deea4b55 * 2026-10-07 promote storefront ddad27dd34 2026-10-07 library billing-api ``` `*` marks the latest revision. `shelf show --revision ` prints an old revision. ## Restore an earlier revision ```console $ shelf restore pdf-tools ddad27dd34 Restored pdf-tools to rev ddad27dd34 (was d4deea4b55). Projects that borrow it keep their revision until they update: shelf propagate pdf-tools ``` `restore` copies the snapshot back over the library copy and makes it the latest again. Unrecorded library edits are recorded first, so nothing is lost; every revision stays in the history. Borrowers change only when they update. ## Related - [Borrowing](https://limyuquan.github.io/shelf/docs/borrowing.md) - [Writing skills](https://limyuquan.github.io/shelf/docs/writing-skills.md) - [`shelf update`](cli/update.md), [`shelf promote`](cli/promote.md), [`shelf propagate`](cli/propagate.md), [`shelf diff`](cli/diff.md), [`shelf log`](cli/log.md), [`shelf restore`](cli/restore.md) --- # Writing skills Source: https://limyuquan.github.io/shelf/docs/writing-skills.md How to create and shape the skills in your library: the Agent Skills format, `shelf new`, editing, linting, what a skill costs in tokens, and renaming, duplicating, archiving and setting a skill's loan length. ## The format A skill is a directory with a `SKILL.md`. shelf follows the [Agent Skills specification](https://agentskills.io/specification): ```markdown --- name: pdf-tools description: "Extract text, tables and form fields from PDFs, and fill or merge PDF files. Use when a task involves reading or producing PDFs." --- # pdf-tools Instructions the agent follows when it uses the skill. ``` | Field | Rules | |---|---| | `name` | Required. 1 to 64 lowercase letters, digits and single hyphens. Must match the directory name. | | `description` | Required. Non-empty, at most 1024 characters. | Anything else in the directory (reference files, scripts, templates) is part of the skill: it is hashed, versioned and copied with it. `.DS_Store`, `Thumbs.db`, `desktop.ini` and `.git` are ignored. shelf never writes its own fields into SKILL.md. Tracking data lives in the lockfile and database, so hashes match the library and strict validators keep accepting the skill. The one exception is `shelf rename` and `shelf duplicate`, which change the library copy's `name:`. ## Create a skill ```console $ shelf new pdf-tools -d "Extract text, tables and form fields from PDFs, and fill or merge PDF files. Use when a task involves reading or producing PDFs." Created pdf-tools at /home/me/.shelf/library/pdf-tools Edit its files with any editor; shelf records each change as a new revision. ``` The new SKILL.md holds the frontmatter and a placeholder body: ```markdown --- name: pdf-tools description: "Extract text, tables and form fields from PDFs, and fill or merge PDF files. Use when a task involves reading or producing PDFs." --- # pdf-tools Describe when and how an agent should apply this skill. ``` In the dashboard, **Library → New skill** does the same and shows the description's session cost as you type. ## Edit a skill Edit the files in `~/.shelf/library//` with any tool, or open the skill in the dashboard, which has an editor for SKILL.md and reference files with a lint strip above it. There is no save step in shelf itself: the next command that reads the library hashes the directory and records a new revision if anything changed. To try an edit inside a project first, edit the borrowed copy there and [promote](https://limyuquan.github.io/shelf/docs/keeping-skills-current.md#promote-a-projects-edits) it when it works. ## Write a good description Agents decide whether to use a skill from its name and description alone, and every session loads the description of every skill available to it. So a description should: - say what the skill does, in concrete terms; - say when to use it: "Use when …"; - stay short. Under 300 characters is a good target. ## Lint `shelf lint` checks SKILL.md files against the format and these conventions: ```console $ shelf lint api-design ~31 description + ~18 body tokens ok git-hygiene ~17 description + ~18 body tokens warning: description doesn't say when to use the skill: add "Use when …" pdf-tools ~32 description + ~17 body tokens ok ``` | Level | Check | |---|---| | error | SKILL.md must start with YAML frontmatter between `---` lines. | | error | The frontmatter must be valid YAML, as `key: value` pairs. | | error | `name` is required, must be a string of at most 64 characters, lowercase letters, digits and single hyphens, and must match the directory. | | error | The directory name must be a valid skill name too. | | error | `description` is required, must be a string, at most 1024 characters. | | warning | The description is longer than 300 characters. | | warning | The description doesn't say when to use the skill (no "when", "whenever", "if you", "if the user", "use for", "use to"). | | warning | The body is empty. | `shelf lint` exits with code 1 when any skill has an error. Warnings don't change the exit code. Name skills to lint only those; with no names it lints the whole library. A library directory that fails the basic checks shelf needs to load a skill (frontmatter present, `name` matching the directory, a `description` of at most 1024 characters) is not loaded at all: other commands skip it, and `shelf doctor` reports it under `library`. `shelf lint` goes by the library's directories, so it still lints such a skill and reports why it doesn't load: ```console $ shelf lint broken broken ~0 description + ~17 body tokens error: description is required ``` ## What a skill costs Token counts are estimates: characters divided by 4, rounded up. | Where | What is counted | |---|---| | `shelf lint` | The description, and the body (everything after the frontmatter), separately. | | `shelf insights`, `shelf suggest`, dashboard session cost | The name plus the description: what loads at every session start. | | `shelf catalog`, `shelf show` | The whole SKILL.md: what loading the skill costs. | The session cost is paid by every project that borrows the skill, in every session. The body is paid only when an agent uses the skill. See [Context budget](https://limyuquan.github.io/shelf/docs/context-budget.md). ## Rename ```sh shelf rename commit-style commit-messages ``` `rename` moves the directory and rewrites only the frontmatter `name:` value, keeping its quoting. Revisions, loans and activity stay attached to the skill, and the changed SKILL.md becomes a new revision. It is refused while any project borrows the skill, because project copies and lockfiles carry the name: ```console $ shelf rename react-best-practices react-patterns error: react-best-practices is borrowed by storefront. Renaming it would orphan their copies and lockfiles. hint: Return it from storefront first (`shelf return react-best-practices` in each project) ``` ## Duplicate ```console $ shelf duplicate git-hygiene commit-style Copied git-hygiene to commit-style at /home/me/.shelf/library/commit-style ``` The copy is a new skill with its own history, starting from the original's current files with `name:` changed. ## Archive ```console $ shelf archive commit-messages Archived commit-messages to /home/me/.shelf/archive/commit-messages-2026-10-07T05-35-15 Its revisions are kept. To restore it, move that directory back into the library. ``` Archiving moves the directory to `~/.shelf/archive/-/`. Nothing is deleted: its revisions stay in the object store, and moving the directory back to `~/.shelf/library/` restores the skill with its history. Like rename, it is refused while the skill is borrowed. An archived skill keeps its name. `new`, `rename`, `duplicate` and `add` refuse to use it (`SKILL_EXISTS`). A new directory with that name made by hand in the library brings the archived skill back and continues its history with the new content. Deleting a skill's directory from the library is treated like archiving, except the files are gone. Archive instead, so you can restore it. ## Loan length Some skills are needed rarely but reliably, such as a migrations skill used once a month. Give them longer loans: ```console $ shelf loan-days sql-migrations 60 sql-migrations: loans last 60 days (set for this skill) $ shelf loan-days sql-migrations --reset sql-migrations: loans last 30 days (the default, config loanDays) ``` The loan length is how long new loans last and how far a use or renewal moves the due date. It is a setting of your library on this machine, capped at `maxLoanDays`. Existing due dates don't change until the skill is next used or renewed. ## Related - [Keeping skills current](https://limyuquan.github.io/shelf/docs/keeping-skills-current.md) - [Importing skills](https://limyuquan.github.io/shelf/docs/importing-skills.md) - [`shelf new`](cli/new.md), [`shelf lint`](cli/lint.md), [`shelf rename`](cli/rename.md), [`shelf duplicate`](cli/duplicate.md), [`shelf archive`](cli/archive.md), [`shelf loan-days`](cli/loan-days.md) --- # Importing skills Source: https://limyuquan.github.io/shelf/docs/importing-skills.md How to bring skills from outside your library into it, safely: `shelf add` from a git repository or directory, `shelf pull` for upstream updates, the local audit and its checks, what agents may and may not import, and linking skills you already have to their upstream. Your library is the trust boundary: agents borrow only from it. Anything from outside goes through a review step first, and nothing enters the library until you pass `--yes`. ## Add a skill ```console $ shelf add gh:someone/skills --skill release-notes release-notes (1 file): review only — nothing imported. Re-run with --yes to import no findings $ shelf add gh:someone/skills --skill release-notes --yes release-notes (1 file): imported into the library no findings ``` The first run fetches the source, finds the skill, audits every file and prints the review. The second run, with `--yes`, copies it into `~/.shelf/library/` as a new skill (revision source `import`) and records where it came from, so `shelf pull` can update it later. ### Sources | Form | Example | |---|---| | GitHub shorthand | `gh:owner/repo`, `gh:owner/repo/path/to/skill`, `gh:owner/repo@v1.2.0` | | GitHub tree URL | `https://github.com/owner/repo/tree/main/skills/pdf-tools` | | Any git URL | `https://…`, `http://…`, `ssh://…`, `git@host:owner/repo.git`, `file://…` | | A local directory | `~/Downloads/pdf-tools`, `../shared-skills` | `--ref` (branch, tag or commit) and `--path` (the skill directory inside the source) override what the source string says. Git sources are cloned shallowly into a temporary directory with credential prompts disabled, so a private repository needs credentials that work without a prompt (an SSH key or a credential helper). A commit that isn't a branch or tag needs a full clone, which shelf falls back to. `git` must be on your `PATH`. ### Choosing skills shelf looks for `SKILL.md` files in the source (or under `--path`), up to four levels deep, skipping `.git` and `node_modules`. When it finds more than one, choose: ```console $ shelf add ./upstream error: The source holds 2 skills: changelog, commit-messages hint: Choose with --skill (comma-separated) or take all with --all ``` `--skill a,b` takes the named ones; `--all` takes every one. Every skill is checked before anything is imported, so a batch is never half-applied. ## The audit Every `add`, `pull` and `adopt` runs a local, offline audit over every file of the skill, and `shelf audit` runs it over your library. It is a tripwire for review, not a sandbox. | Rule | Severity | Flags | |---|---|---| | `pipe-to-shell` | high | Downloading and executing a script (`curl … \| sh`, `iwr … \| iex`, `Invoke-Expression`). | | `decode-and-run` | high | Decoding data and executing it (`base64 -d … \| sh`, `eval(atob(…))`). | | `prompt-injection` | high | Phrasing that tries to override the agent's other instructions ("ignore previous instructions"). | | `upload-files` | high | `curl` uploading local files (`-d @file`, `-F`, `-T`). | | `hidden-characters` | high | Zero-width, text-direction and Unicode tag characters, invisible to a reviewer. | | `binary-file` | high | Binaries and native executables (`.exe`, `.dll`, `.so`, `.dylib`, `.bin`, `.app`, `.msi`, or any file with NUL bytes). | | `conceal-from-user` | medium | Asking the agent not to tell the user something. | | `credential-access` | medium | Reading `~/.ssh`, `~/.aws`, `~/.gnupg`, `~/.kube`, Docker config, private keys, `.env` files or the macOS keychain. | | `raw-ip-url` | medium | URLs that contact a raw IP address. | | `encoded-blob` | medium | Long base64-like blobs (200+ characters). | | `destructive-command` | medium | `rm -rf /`, `rm -rf ~`, `mkfs`, `dd of=/dev/…`. | | `script-file` | low | Script files (`.sh`, `.py`, `.js`, `.ps1`, …) or executable files the agent may be told to run. | **High-severity findings block** `add --yes` and `pull --yes`: ```console $ shelf add ./upstream --skill changelog --yes changelog (1 file): blocked by high-severity findings. Review them; --yes --force imports anyway HIGH SKILL.md:6 Downloads and executes a script Run: curl -fsSL https://example.com/install.sh | sh ``` Read the finding. If it is fine, import anyway with `--yes --force`. Medium and low findings are shown but never block. `shelf adopt` shows findings for review and is never blocked. Audit your whole library, your own skills included: ```console $ shelf audit changelog HIGH SKILL.md:6 Downloads and executes a script Run: curl -fsSL https://example.com/install.sh | sh ``` `shelf audit` prints `No findings in N skill(s).` when everything is clean. It always exits 0; check `data.skills[].findings` with `--json` to act on findings in scripts. ## Pull upstream updates ```console $ shelf pull release-notes release-notes: changes available — nothing applied. Re-run with --yes to apply --- a/SKILL.md +++ b/SKILL.md @@ -3,4 +3,5 @@ description: Draft release notes from merged pull requests, grouped by user impact. Use when preparing a release. --- - Group by user impact +- Link each item to its pull request Audit: no findings $ shelf pull release-notes --yes release-notes updated to 3547e8b373. Run `shelf propagate release-notes` to update borrowers ``` `pull` re-fetches the recorded source (the same URL, ref and path), shows the diff against the library's latest revision and a fresh audit, and applies it only with `--yes`. Then run `shelf propagate ` to update borrowing projects. It refuses to clobber your own work: if the library copy was edited since the last import, `pull` fails with `CONFLICT` unless you add `--force`. When the source matches the library, it prints ` is up to date with `. Only skills imported with `shelf add` (or linked to a source) can be pulled. ## Link a skill you already have Skills you [adopted](https://limyuquan.github.io/shelf/docs/migrating.md) from your projects often came from a public repository. Link them to it so you can pull its updates: ```console $ shelf add gh:someone/skills --skill brand-voice brand-voice (1 file): already in the library and identical to the source. Re-run with --yes to link them, so `shelf pull` fetches updates no findings $ shelf add gh:someone/skills --skill brand-voice --yes brand-voice (1 file): linked to this source (library unchanged). `shelf pull` now fetches its updates no findings ``` When the library already has a skill with the same name, `add --yes` records the source without changing the library. If the source differs, the review says in how many files. The library's current revision counts as the last import, so the next `shelf pull` offers the upstream version as a reviewed update. A skill can be linked to one source. Adding again fails with `SKILL_EXISTS`; use `shelf pull`. So does adding a skill with an archived skill's name: restore the archived skill by moving it back from `~/.shelf/archive/` (`add --yes` then links it), or restore it and `shelf rename` it to free the name. ## What agents may do Agents (any actor starting with `agent:`) may run the review step of `add` and `pull`, and may link existing skills. They may not change library content from a git source, meaning `add --yes` of a new skill or `pull --yes`, unless you set `allowAgentImports` to `true` in the [config](https://limyuquan.github.io/shelf/docs/configuration.md): ```console $ shelf add gh:someone/skills --skill seo-checklist --yes error: Agents may review skills from remote sources but not import them hint: Show the user the review and ask them to run the command with --yes themselves, or to set allowAgentImports in ~/.shelf/config.json ``` The agent shows you the review and the exact command, and you run it. Imports from a local directory are not restricted. The guide also tells agents never to pass `--force` on high-severity findings without your explicit approval. This rule depends on shelf knowing an agent is acting. Agents are detected from their environment (see [Environment](https://limyuquan.github.io/shelf/docs/environment.md)), and `--actor` can override that, so treat it as a guard rail for well-behaved agents, not as a security boundary against a hostile one. ## Related - [Migrating](https://limyuquan.github.io/shelf/docs/migrating.md) - [`shelf add`](cli/add.md), [`shelf pull`](cli/pull.md), [`shelf audit`](cli/audit.md) --- # Migrating existing skills Source: https://limyuquan.github.io/shelf/docs/migrating.md How to bring skills you already copied into projects by hand under shelf: find every copy with `shelf scan`, adopt them with `shelf adopt` (or the dashboard's Find existing skills), and sort out copies that drifted apart. ## Find your copies ```console $ shelf scan ~/code brand-voice 3 copies, 2 version(s) ff3ffb1a29 ~/code/analytics/.claude/skills/brand-voice ~/code/analytics/.agents/skills/brand-voice 3b83bd78e1 ~/code/marketing-site/.claude/skills/brand-voice git-hygiene 2 copies, 1 version(s), in library 174f0076e3 (library latest) ~/code/storefront/.claude/skills/git-hygiene [managed] ~/code/storefront/.agents/skills/git-hygiene [managed] Adopt a copy into the library and manage its project's copies: shelf adopt ``` `scan` walks the directory (default: the current one, 6 levels deep, `--depth N` to change) and finds every folder with a `SKILL.md` inside a `skills` directory. It groups them by name, then by content: - **More than one version** means the copies drifted apart. - **(library latest)** and **(old library revision)** mark versions your library already knows. - **[managed]** marks copies a project's lockfile already tracks. It skips dependency, build and cache directories (`node_modules`, `vendor`, `dist`, `build`, `target`, `.venv`, `.git`, `Library` and others) and your shelf home. Scanning never changes anything. ## Adopt ```console $ shelf adopt ~/code/marketing-site/.claude/skills/brand-voice ~/code/analytics/.claude/skills/brand-voice brand-voice: imported into the library now borrowed by marketing-site (current) brand-voice: differs from the library (kept as local edits; if it is only an older version, `shelf update --force` replaces it) now borrowed by analytics (modified) ``` For each path, in the order given, `adopt`: 1. Imports the skill into the library if the library has no skill of that name. The **first copy adopted becomes the library version.** 2. Compares the copy with the library otherwise, and never overwrites the library. 3. If the copy sits in a project skill directory (`/./skills/`), registers the project (creating its lockfile) and turns the copy into a loan. The project's other target directories get the same content, so every copy of the loan starts identical. The loan is pinned, with a fresh loan period. 4. Audits the copy and shows any findings for review (adopting is never blocked). Each copy gets one of four outcomes: | Outcome | Meaning | Loan state | |---|---|---| | `imported` | The library had no such skill; this copy became it. | `current` | | `matched` | The copy is identical to the library's latest revision. | `current` | | `older` | The copy is an earlier revision the library knows, or any differing copy when adopting with `--unedited`. | `behind` | | `differs` | The library has a different version; the copy keeps its content as local edits. | `modified` | A copy outside a project skill directory is imported but gets no loan (`no loan: Not inside a project skill directory`). So does a copy whose project is nested inside another shelf project. ## Drifted copies When copies differ, decide whether the differences are edits someone made, or just versions installed at different times. **They are edits.** Adopt the best copy first, then the rest. The others become `modified` loans, so nothing is lost. In each project, review and choose: ```sh shelf diff brand-voice # what this copy changed shelf promote brand-voice --propagate # this copy is better: publish it everywhere shelf update brand-voice --force # the library's is better: take it ``` **They are older versions.** If the copies were never edited and only differ because they were installed from upstream at different times, adopt the newest first with `--unedited`: ```sh shelf adopt --unedited ~/code/marketing-site/.claude/skills/brand-voice ~/code/analytics/.claude/skills/brand-voice ``` Every other copy is then recorded as an older revision (source `adopt`), its loan is `behind`, and a plain `shelf update` brings it up to date. Agents are never invited to promote stale content. ## In the dashboard **Library → Find existing skills** (or ⌘K, "Find existing skills") does the same with checkboxes. It scans the folder that holds your registered projects by default, groups copies by name and version, and adopts the copies you tick. Within each skill it adopts the library's (or the most common) version first. The checkbox **These copies have no local edits (older versions)** is `--unedited`. See [Dashboard](https://limyuquan.github.io/shelf/docs/dashboard.md#find-existing-skills). ## Link to upstream Skills that came from a public repository can be linked to it after adopting, so you can pull its updates later: ```sh shelf add gh:owner/repo --skill brand-voice --yes ``` On a skill the library already has, this only records the source. See [Importing skills](https://limyuquan.github.io/shelf/docs/importing-skills.md#link-a-skill-you-already-have). ## Afterwards - Commit each project's `.agents/shelf.lock.json`. - Run `shelf status` in each project, or open the dashboard's Attention page, to see what still needs a decision. - User-level copies in `~/.claude/skills` and similar folders load in every project. Once a skill is in your library, consider borrowing it only where it is needed and removing the user-level copy. `shelf insights` lists them under "Loaded everywhere". ## Related - [`shelf scan`](cli/scan.md), [`shelf adopt`](cli/adopt.md) - [Keeping skills current](https://limyuquan.github.io/shelf/docs/keeping-skills-current.md) --- # Sets Source: https://limyuquan.github.io/shelf/docs/sets.md Sets are named groups of library skills that you borrow together in one step, such as `frontend` for your React, Playwright and accessibility skills. This page covers creating, nesting, borrowing and deleting them. ## Create a set ```console $ shelf set save frontend react-best-practices playwright-testing -d "UI work in React apps" Saved frontend (2 skills): playwright-testing, react-best-practices Borrow it with `shelf borrow @frontend` ``` Set names follow the skill-name rules (lowercase letters, digits and single hyphens). Every skill must be in the library. `-d` sets a description; without it, an existing set keeps its description. Saving a set that exists replaces its skills. To add one skill, save the set again with the full list. ## Build on another set A skill argument starting with `@` includes every skill of another set: ```console $ shelf set save web @frontend api-design Saved web (3 skills): api-design, playwright-testing, react-best-practices ``` The set stores the expanded list of skills, not a reference: changing `frontend` later doesn't change `web`. ## List sets ```console $ shelf set list NAME SKILLS DESCRIPTION backend api-design, git-hygiene, sql-migrations frontend accessibility-audit, playwright-testing, react-best-practic… UI work in React apps ``` Archived skills are left out of a set's list. ## Borrow a set ```console $ shelf borrow @frontend Borrowed playwright-testing (due 2026-11-06) → .agents/skills, .claude/skills Borrowed react-best-practices (due 2026-11-06) → .agents/skills, .claude/skills ``` Each skill becomes an ordinary loan with its own due date, renewals and lockfile entry. Nothing about the set reaches the project: returning one skill doesn't affect the others, and the lockfile never mentions sets. You can mix sets and skills: `shelf borrow @frontend pdf-tools`. `shelf keep @frontend` keeps every skill of the set that the project borrows. ## Delete a set ```console $ shelf set delete web Deleted the set web ``` Deleting a set never touches loans or skills. ## Where sets live Sets are stored in your database (`~/.shelf/shelf.db`), like your library, so they are per machine. Agents use sets only when you name one; the guide tells them not to create or change sets unless asked. In the dashboard, the Library page has a **Sets** section to create, edit and delete sets, and the borrow dialog has a chip per set. ## Related - [`shelf set`](cli/set.md), [`shelf borrow`](cli/borrow.md) --- # Context budget Source: https://limyuquan.github.io/shelf/docs/context-budget.md What skills cost in an agent's context, and how to keep that cost down: what loads at session start and what loads on use, `shelf insights`, `shelf suggest`, and when keeping a skill is worth it. ## What loads when Agents see skills in two steps: | When | What loads | Cost per skill | |---|---|---| | Every session start | The name and description of every skill available to the agent | Small, but paid in every session of every project that has the skill | | When the agent uses the skill | The full SKILL.md (and any reference files it reads) | Larger, paid only when it helps | So the cost that adds up is the session cost: every borrowed skill, and every user-level skill in folders like `~/.claude/skills`, adds its description to every session. shelf's due dates exist to stop that list growing forever. Token counts in shelf are estimates: characters divided by 4, rounded up. They are good for comparing skills and projects, not exact counts. ## shelf insights Inside a project, `shelf insights` shows that project: ```console $ shelf insights billing-api: ~226 tokens of skill descriptions load at every session start ~136 from skills loaded everywhere (shelf, frontend-design, web-research) ~90 from 4 borrowed skills; the full SKILL.md loads only when used SKILL SESSION ON USE ACTIVE DAYS (30D) LAST USED api-design ~25 ~127 10 today sql-migrations ~25 ~127 3 2d ago release-notes ~21 ~114 1 9d ago git-hygiene ~19 ~129 0 never Every project and library skill: shelf insights --all ``` | Column | Meaning | |---|---| | SESSION | Name plus description of the revision the project holds. | | ON USE | The whole SKILL.md. | | ACTIVE DAYS (30D) | Days in the last 30 (UTC) on which an agent used the skill in this project. | | LAST USED | The last recorded use in this project. | "Skills loaded everywhere" are the user-level skills found in every harness's user skill directory (`~/.claude/skills`, `~/.agents/skills`, `~/.copilot/skills`, `~/.config/opencode/skills` and the rest). A skill present in several of them counts once. Outside a project, or with `--all`, it shows every project and every library skill: ```console $ shelf insights --all Context at session start (skill names and descriptions; tokens estimated as chars / 4) PROJECT TOTAL BORROWED EVERYWHERE SKILLS storefront ~244 ~108 ~136 4 billing-api ~226 ~90 ~136 4 mobile-app ~194 ~58 ~136 2 docs-site ~179 ~43 ~136 2 Library skills, last 30 days SKILL ACTIVE DAYS LAST USED PROJECTS SESSION ON USE react-best-practices 12 today 2 ~37 ~195 api-design 10 today 1 ~25 ~127 playwright-testing 3 1d ago 1 ~27 ~143 release-notes 3 today 3 ~21 ~114 sql-migrations 3 2d ago 1 ~25 ~127 accessibility-audit 2 18d ago 1 ~25 ~122 commit-messages 0 never 0 ~20 ~48 never used git-hygiene 0 never 2 ~19 ~129 never used pdf-tools 0 34d ago 1 ~22 ~116 unused 30d Loaded everywhere (every session, every project): shelf ~66, frontend-design ~50, web-research ~20 ``` `never used` means no use was ever recorded for the skill; `unused 30d` means none in the last 30 days. Both are candidates for returning. Usage comes from the hooks, so without hooks (or `shelf used`), every skill looks unused. The dashboard's **Insights** page shows the same data with a per-project bar, a 30-day chart of skills used per day, and a sortable skills table. ## shelf suggest ```console $ shelf suggest SKILL WHY SESSION COST pdf-tools package.json depends on pdf-lib ~22 tok playwright-testing package.json depends on @playwright/test ~27 tok Borrow one with `shelf borrow `. ``` `suggest` reads what the project is built with and matches it against library skills it doesn't borrow yet: - **Dependencies** from `package.json` (dependencies and devDependencies), `pyproject.toml` (PEP 621, dependency groups, Poetry), `requirements*.txt`, `Cargo.toml` and `go.mod`. - **Well-known files and folders**: `convex/`, `supabase/`, `migrations/`, `prisma/schema.prisma`, `.github/workflows`, `playwright.config.*`, `next.config.*`, `tailwind.config.*`, `vite.config.*`, `vitest.config.*`, `drizzle.config.*`, `Dockerfile` or `compose.yaml`, `Cargo.toml`, `pyproject.toml`, `go.mod`. It reads the project root, its direct subdirectories, and packages under `packages/`, `apps/`, `services/`, `libs/`, `crates/` and `modules/` (at most 64 directories). A term matching a word of a skill's name ranks above one appearing in its description. Common terms (`typescript`, `eslint`, `react-dom`, `@types/*` and others) are ignored. It never uses the network. Suggestions are hints for you. Borrow what the work at hand needs, not everything that matches. ## When to keep A kept skill never expires, so its description loads into every session from then on. Keep a skill only when it covers something the project is built on: the framework, database or platform in its manifest. For example, Convex skills in a project whose `package.json` depends on `convex`. Don't keep a skill because a task touched it once, or "just in case". An ordinary loan renews itself whenever it is used, so a skill the project really uses never expires anyway. Agents follow the same rule and say which dependency in `--reason`. ## Reducing the cost - Return skills that show `never used` or `unused 30d`: `shelf return `. Or let them expire. - Shorten long descriptions. `shelf lint` warns above 300 characters. - Move skills out of user-level folders (which load in every project) into your library, and borrow them where they are needed. See [Migrating](https://limyuquan.github.io/shelf/docs/migrating.md). - Lower `loanDays` in the [config](https://limyuquan.github.io/shelf/docs/configuration.md), or a skill's own length with `shelf loan-days 14`, so unused skills leave sooner. Skills in use still renew themselves. ## Related - [`shelf insights`](cli/insights.md), [`shelf suggest`](cli/suggest.md), [`shelf keep`](cli/keep.md) - [Writing skills](https://limyuquan.github.io/shelf/docs/writing-skills.md#what-a-skill-costs) --- # Hooks Source: https://limyuquan.github.io/shelf/docs/hooks.md The harness hooks that `shelf setup` installs in Claude Code and Codex: what they do, what they cost, the exact entries they add, trusting them in Codex, what to do in harnesses without hooks, and how to check them. ## What they do Two hooks make loans work without anyone thinking about them: | Hook | Runs on | Does | |---|---|---| | `shelf hook session-start` | Session start | Syncs the project (returns overdue skills, restores missing copies, updates `--follow` loans) and prints one `shelf: …` line only when something needs attention. | | `shelf hook skill-use` | After tool calls, and when you submit a prompt | Recognises a borrowed skill being used and renews its loan. Prints nothing. | A use is recognised when: - the Skill tool is called with a borrowed skill's name; - a tool's input mentions a path inside a borrowed copy, such as a Read of `.agents/skills/pdf-tools/SKILL.md` or `cat .claude/skills/pdf-tools/reference.md` in a shell command; - your prompt invokes a borrowed skill as `/pdf-tools` or `$pdf-tools`. Each recognised use moves the loan's due date to its loan length from now (30 days by default), so a loan only comes due after going unused. See [Concepts](https://limyuquan.github.io/shelf/docs/concepts.md#renew-on-use). ## What they cost Hook output is the only thing hooks add to an agent's context. - `skill-use` never prints. It also exits before opening any file or database for ordinary tool calls (edits, searches, commands that don't mention `skills`), so it adds almost no time to a turn. - `session-start` prints nothing for a healthy project. When something needs attention, it prints one line, with at most four skill names per item: ``` shelf: due soon unless used: git-hygiene (4d) — `shelf renew ` to keep, `shelf return ` if unneeded; edited here: react-best-practices — `shelf promote ` publishes to the library. Details: `shelf status`. ``` The line can report, in this order: skills just returned after going unused, skills due soon, overdue skills kept because of local edits, skills edited here, skills edited here and in the library, and skills with library updates. The bundled skill tells agents to act on it. Hooks never fail the agent's turn: errors go to stderr (the harness's debug log) and the exit code is always 0. Outside a shelf project they do nothing. ## Install ```console $ shelf setup … Hooks (renew skills when used, report loans needing attention): Claude Code: hooks installed (/home/me/.claude/settings.json) Codex: hooks installed (/home/me/.codex/hooks.json) Codex runs new hooks only after you trust them: open `/hooks` in Codex once. ``` `shelf setup` adds the hooks for each harness whose config directory exists: `~/.claude` (or `$CLAUDE_CONFIG_DIR`) and `~/.codex` (or `$CODEX_HOME`). If neither exists, it says so; run `shelf setup` again after installing one. Your other settings and hooks are kept. Hook entries are recognised by their command (`shelf hook …`), so running `shelf setup` again replaces them, for example after the binary moved, and never duplicates them. ### Claude Code Added to `~/.claude/settings.json`: ```json { "hooks": { "SessionStart": [ { "hooks": [{ "type": "command", "command": "/home/me/.local/bin/shelf hook session-start --harness claude-code", "timeout": 30 }] } ], "PostToolUse": [ { "matcher": "Skill|Read|Bash", "hooks": [{ "type": "command", "command": "/home/me/.local/bin/shelf hook skill-use --harness claude-code", "timeout": 10 }] } ], "UserPromptSubmit": [ { "hooks": [{ "type": "command", "command": "/home/me/.local/bin/shelf hook skill-use --harness claude-code", "timeout": 10 }] } ] } } ``` ### Codex Added to `~/.codex/hooks.json`. The same events, but `PostToolUse` has no matcher, because Codex reads skills with shell commands whose tool names vary: ```json { "hooks": { "SessionStart": [ { "hooks": [{ "type": "command", "command": "/home/me/.local/bin/shelf hook session-start --harness codex", "timeout": 30 }] } ], "PostToolUse": [ { "hooks": [{ "type": "command", "command": "/home/me/.local/bin/shelf hook skill-use --harness codex", "timeout": 10 }] } ], "UserPromptSubmit": [ { "hooks": [{ "type": "command", "command": "/home/me/.local/bin/shelf hook skill-use --harness codex", "timeout": 10 }] } ] } } ``` **Codex runs new hooks only after you trust them.** Open `/hooks` in Codex once and trust shelf's entries. Until then, Codex loans keep their calendar due dates. ### The command path The hook command is the absolute path of the running binary (quoted if it contains spaces), because hooks may run without your shell's `PATH`, for example from a desktop app. With npm, that is the platform binary inside the npm package. If you move or replace the binary at another path, run `shelf setup` again or `shelf doctor --fix`. ### Settings files shelf can't read If `settings.json` or `hooks.json` exists but isn't a JSON object, shelf leaves it alone and reports the harness as `skipped`. Fix the file, or add the entries above by hand, then check with `shelf doctor`. ## Remove ```console $ shelf setup --no-hooks … Hooks: Claude Code: hooks removed (/home/me/.claude/settings.json) Codex: hooks removed (/home/me/.codex/hooks.json) ``` This removes only shelf's entries and sets `"hooks": false` in the config, so `shelf doctor` stops expecting them. `shelf setup` without the flag turns them back on. ## Harnesses without hooks Cursor, Gemini CLI, Copilot and the other harnesses read borrowed skills from `.agents/skills` like any other, but shelf has no hooks for them. There: - Loans keep their calendar due dates. Nothing renews them on use. - Agents run `shelf used ` after using a borrowed skill, which renews it like a hook would. The guide tells them to. - Agents can run `shelf status --json` at the start of a task to see what needs attention, since no session-start note arrives. - Overdue skills are returned when someone runs `shelf status` or `shelf sync` in the project, or by a scheduled `shelf sweep` (see [Automation](https://limyuquan.github.io/shelf/docs/automation.md)). ## Check ```console $ shelf doctor … ok hooks: Hooks renew loans on use in every installed harness … ``` `shelf doctor` warns when a harness's hooks are missing, point at another binary, or its settings file isn't valid JSON. `shelf doctor --fix` reinstalls them. The dashboard's **Settings** page shows each harness's hook status (Installed, Not installed, Outdated, Settings unreadable, Not installed on this machine) and has a **Repair** button. ## Related - [`shelf setup`](cli/setup.md), [`shelf hook`](cli/hook.md), [`shelf used`](cli/used.md), [`shelf doctor`](cli/doctor.md) - [Agents](https://limyuquan.github.io/shelf/docs/agents.md) --- # Harnesses Source: https://limyuquan.github.io/shelf/docs/harnesses.md Where each coding agent looks for skills, which directories shelf writes to, how to add a harness's directory to a project with `shelf targets`, link mode, and why shelf copies skills instead of symlinking them. ## Default targets shelf writes borrowed skills to two directories in each project: | Target | Read by | |---|---| | `.agents/skills` | The cross-harness convention: Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode, Amp, Goose, Cline, Roo Code, Factory Droid, Windsurf / Devin | | `.claude/skills` | Claude Code (and Cursor, Copilot, OpenCode, Amp, Goose and Cline also read it) | These two cover every harness below except Kiro and a few that document neither directory. Change the default for all your projects with `targets` in the [config](https://limyuquan.github.io/shelf/docs/configuration.md); change it for one project with `shelf targets`. ## Where each harness looks As of October 2026: | Harness | Project dirs | Reads `.agents/skills` | Reads `.claude/skills` | |---|---|---|---| | Claude Code | `.claude/skills` | No | Yes | | Codex CLI | `.agents/skills` (each folder from the working dir up to the repo root) | Yes | No | | Cursor | `.agents/skills`, `.cursor/skills` | Yes | Yes | | Gemini CLI | `.gemini/skills`, `.agents/skills` (trusted workspaces only) | Yes | No | | GitHub Copilot | `.github/skills`, `.claude/skills`, `.agents/skills` | Yes | Yes | | OpenCode | `.opencode/skills`, `.claude/skills`, `.agents/skills` | Yes | Yes | | Amp | `.agents/skills`, `.claude/skills` | Yes | Yes | | Windsurf / Devin | `.devin/skills`, `.windsurf/skills` | Yes | When Claude config is enabled | | Goose | `.agents/skills`, `.goose/skills`, `.claude/skills` | Yes | Yes | | Cline | `.clinerules/skills`, `.cline/skills`, `.claude/skills`, `.agents/skills` | Yes | Yes | | Roo Code | `.roo/skills`, `.agents/skills` | Yes | Undocumented | | Factory Droid | `.factory/skills`, `.agents/skills` | Yes | Undocumented | | Kiro | `.kiro/skills` | Undocumented | Undocumented | ## Harness ids `shelf targets` knows these harnesses. Use the id (or the directory) with `--add` and `--remove`. | Id | Harness | Project directory | User directory | Reads `.agents/skills` | |---|---|---|---|---| | `agents` | Agent Skills convention | `.agents/skills` | `~/.agents/skills` | yes (default target) | | `claude` | Claude Code | `.claude/skills` | `~/.claude/skills` | no (default target) | | `kiro` | Kiro | `.kiro/skills` | `~/.kiro/skills` | no | | `github` | GitHub Copilot | `.github/skills` | `~/.copilot/skills` | yes | | `cursor` | Cursor | `.cursor/skills` | `~/.cursor/skills` | yes | | `gemini` | Gemini CLI | `.gemini/skills` | `~/.gemini/skills` | yes | | `opencode` | OpenCode | `.opencode/skills` | `~/.config/opencode/skills` | yes | | `devin` | Windsurf / Devin | `.devin/skills` | `~/.config/devin/skills` | yes | | `windsurf` | Windsurf (legacy) | `.windsurf/skills` | `~/.codeium/windsurf/skills` | yes | | `roo` | Roo Code | `.roo/skills` | `~/.roo/skills` | yes | | `cline` | Cline | `.cline/skills` | `~/.cline/skills` | yes | | `factory` | Factory Droid | `.factory/skills` | `~/.factory/skills` | yes | | `goose` | Goose | `.goose/skills` | `~/.config/goose/skills` | yes | | `junie` | Junie | `.junie/skills` | `~/.junie/skills` | no | | `qwen` | Qwen Code | `.qwen/skills` | `~/.qwen/skills` | no | | `trae` | Trae | `.trae/skills` | `~/.trae/skills` | no | | `openhands` | OpenHands | `.openhands/skills` | `~/.openhands/skills` | no | The user directories matter in two places: `shelf setup` installs the bundled `shelf` skill in `~/.agents/skills` and `~/.claude/skills`, plus the user directory of any installed harness that doesn't read `.agents/skills` (for example `~/.kiro/skills` when `~/.kiro` exists). And `shelf insights` counts the skills in all of them as "loaded everywhere". Only Claude Code and Codex get [hooks](https://limyuquan.github.io/shelf/docs/hooks.md). ## Project targets ```console $ shelf targets Skills are written to: .agents/skills, .claude/skills (your default) HARNESS DIRECTORY NOTE agents .agents/skills on detected claude .claude/skills on detected kiro .kiro/skills detected github .github/skills reads .agents/skills cursor .cursor/skills reads .agents/skills … openhands .openhands/skills Kiro is used here but does not read .agents/skills: shelf targets --add kiro ``` `detected` means the harness's config directory (such as `.kiro/`) exists in the project. shelf suggests adding a detected harness that doesn't read `.agents/skills` and isn't already covered. ```console $ shelf targets --add kiro Skills are written to: .agents/skills, .claude/skills, .kiro/skills … ``` Changing targets moves every loan in the project: copies appear in added directories and are removed from removed ones. The project's targets are written to its lockfile (`"targets": [...]`), so every clone uses them. | Option | Effect | |---|---| | `--add ids` | Add harness ids or project-relative directories (comma-separated). | | `--remove ids` | Remove them. Copies with local edits are refused unless `--force`. | | `--reset` | Go back to your default targets from the config, and drop the lockfile's `targets`. | | `--force` | Remove copies even if they have local edits. | A directory that isn't a known harness works too, as long as it is inside the project: `shelf targets --add tools/agent-skills`. A project needs at least one target. ### Symlinked harness directories Some projects symlink one harness directory to another, such as `.claude/skills` → `.agents/skills`. shelf compares targets by their real path: aliases are written once, never turned into a link to themselves, and removing one name never deletes the copy behind the other. `shelf targets` notes such a harness as `same directory as … (symlink)`. ## Link mode By default every target holds its own copy. In link mode, the first target holds the only real copy and the others are relative symlinks to it (junctions on Windows): ```console $ shelf borrow pdf-tools --link Borrowed pdf-tools (due 2026-11-06) → .agents/skills, .claude/skills $ ls -l .claude/skills lrwxrwxrwx 1 me me 30 Oct 7 13:35 pdf-tools -> ../../.agents/skills/pdf-tools ``` Use `--link` per loan, or `"mode": "link"` in the [config](https://limyuquan.github.io/shelf/docs/configuration.md) for every new loan. The lockfile records `"mode": "link"` for such loans. Hashing follows symlinks, so content states work the same in both modes. ## Why copies, not symlinks Symlinked skills have open or recent bugs in Cursor, Claude Code and Codex, mostly on Windows, and Windows-native apps can't follow Linux symlinks inside WSL. Copies work everywhere, and shelf tracks them by hash, so they can't drift unnoticed. Use link mode in projects where you know symlinks work. ## Frontmatter The Agent Skills spec requires `name` (1 to 64 characters of `[a-z0-9-]`, matching the directory) and `description` (at most 1024 characters). The reference validator rejects unknown top-level keys, and some harnesses silently skip skills that fail validation. shelf therefore never writes its own fields into SKILL.md; all tracking data lives in the lockfile and the database. ## Adding a harness to shelf Harnesses are one entry each in `packages/core/src/projection/harnesses.ts`. Add the entry and a row to the tables on this page. See [Architecture](https://limyuquan.github.io/shelf/docs/architecture.md#extending). ## Sources - Spec: https://agentskills.io/specification - Claude Code: https://code.claude.com/docs/en/skills - Codex: https://learn.chatgpt.com/docs/build-skills - Cursor: https://cursor.com/docs/context/skills - Gemini CLI: https://geminicli.com/docs/cli/skills/ - Copilot: https://docs.github.com/en/copilot/concepts/agents/about-agent-skills - OpenCode: https://opencode.ai/docs/skills/ - Amp: https://ampcode.com/docs/customize/skills - Windsurf / Devin: https://docs.devin.ai/desktop/cascade/skills - Goose: https://goose-docs.ai/docs/guides/context-engineering/using-skills - Cline: https://docs.cline.bot/features/skills - Roo Code: https://roocodeinc.github.io/Roo-Code/features/skills - Factory: https://docs.factory.com/cli/configuration/skills - Kiro: https://kiro.dev/docs/skills/ --- # Dashboard Source: https://limyuquan.github.io/shelf/docs/dashboard.md The local dashboard that `shelf ui` opens shows everything shelf manages: loans that need you, projects, the library editor, revisions, activity, insights and settings. This page covers every page, the keyboard shortcuts, live updates, the token and security model, and phones. ## Start it ```console $ shelf ui shelf dashboard: http://127.0.0.1:4222/?token= Ctrl-C to stop. ``` shelf opens the URL in your browser (`open` on macOS, `cmd.exe /c start` in WSL, `xdg-open` elsewhere) and serves until you press Ctrl-C. | Option | Effect | |---|---| | `--port N` | Listen on this port. Without it, the OS picks a free port each time. | | `--no-open` | Print the URL without opening a browser. | | `--rotate-token` | Replace the access token, signing out every browser. | | `--json` | Print the URL as a JSON envelope. | Pass a fixed `--port` if you want a bookmark that keeps working: the token survives restarts, but a random port doesn't. Everything is built into the binary (no Node, no CDN) and works offline. The dashboard calls the same core functions as the CLI, so it behaves exactly like the commands. Changes you make there are recorded as `user:dashboard` in the activity log. Viewing never changes anything: unlike `shelf status`, opening a project page doesn't return overdue loans. ## Attention The home page (`/`) lists every loan, across all your projects, that needs a decision, grouped by its most urgent reason: | Section | Loans | |---|---| | Overdue | Past their due date, not yet returned: either the copy has local edits, which expiry never discards, or shelf hasn't synced the project since (the next session start, `shelf status` or `shelf sync` there returns it). The row says which. | | Diverged | Edited in the project and in the library. | | Edited in a project | The project copy has local edits. | | Missing copies | Copies were deleted. | | Due soon | Due within `dueSoonDays`, unused for a while. | | Updates available | The library has a newer revision. | Each row shows the skill, the project, why it is listed, and the due date. Hover a row (or look on a touch screen) for its quick action: **Renew**, **Review** or **Sync**. The **…** menu has every action for the loan (see [Project](https://limyuquan.github.io/shelf/docs/dashboard.md#project)). **Review** opens the changes dialog with a per-file diff: - Local edits: **Promote to library** (with **Also update other projects that borrow it**, on by default) or **Discard edits**. - Edited here and in the library: **Keep this project's** (promote with force) or **Take the library's**. - Library update: **Update to latest**. A banner warns when Claude Code or Codex has no shelf hooks, with a **Fix in settings** button. When nothing needs you, the page says **All caught up**. ## Projects `/projects` lists every registered project with its overdue and due-soon counts, number of skills and when shelf last saw it. A project whose directory is gone is marked **missing**. Projects are registered with `shelf init` (or by running any shelf command in a clone); the dashboard can't add them. ## Project A project page shows its **Borrowed skills**: state (Up to date, Behind, Edited, Diverged, Missing), due date or **Kept**, and last use. Click an Edited, Diverged or Behind pill to review the changes. Each loan's **…** menu: | Item | Does | |---|---| | Review edits… / Review library changes… | Opens the changes dialog. | | Renew for N days | `shelf renew` with the skill's loan length. | | Extend by a week | `shelf due +7d`. | | Keep — never expires / Stop keeping | `shelf keep` / `shelf keep --off`. | | Update to latest | `shelf update`. | | Return / Return and delete edits | `shelf return` / `shelf return --force`. Return runs at once; Return and delete edits asks for confirmation first, like bulk return (tick **Also delete local edits**). | **Borrow skills** (or `b`) opens the borrow dialog: filter the library, toggle whole sets with their chips, see suggested skills first with their session cost, tick **Keep (never expires)** if wanted, and borrow. The dialog has no options for loan days, `--follow` or `--link`; use the CLI for those. **Suggested for this project** lists library skills that match the project's dependencies and files, with the reason and session cost, and a **Borrow** button. See [Context budget](https://limyuquan.github.io/shelf/docs/context-budget.md#shelf-suggest). ### Bulk actions On Attention and project pages, tick several loans (or press `x`, shift-click for a range) and use the bar: **Renew**, **Update**, **Keep** / **Stop keeping**, **Return**. An action that applies to only some of the selection says how many. Bulk return asks for confirmation and leaves edited loans alone unless you tick **Also delete local edits**. One summary toast reports the result; failed rows stay selected. ## Library `/library` lists your skills with their borrower count and session cost. The filter (`/`) searches inside skills too, SKILL.md and reference files, and shows the matching lines; click one to open that file. The page also has the **Sets** section (create, edit, delete; deleting offers **Undo**), **New skill**, and **Find existing skills**. ### Skill page - **Editor.** SKILL.md and reference files open in an editor. Save with ⌘S (Ctrl+S); each save is a new revision. Leaving with unsaved changes asks first. - **Lint strip.** Above SKILL.md, updated as you type: description and body tokens, format errors and warnings (the same rules as `shelf lint`). - **Details.** Revision, number of revisions, tokens, **Loan length** (Default, 7, 14, 30, 60 or 90 days, capped at `maxLoanDays`), source and location. - **Borrowed by.** Each borrowing project and its state. Tick the ones that are behind and **Update N to latest** (like `shelf propagate --project`). The **+** button borrows the skill into another project. - **History.** Every revision; each opens its revision page. - **Check for updates.** For skills linked to a source: fetches, audits and shows the diff, then **Apply to library** (high-severity findings need an extra checkbox). - **…** menu: **Rename…**, **Duplicate…**, **Archive…**. Rename and archive are refused while the skill is borrowed, and list the borrowers. ### Revision page Each revision shows what it changed, its files (read-only) and its metadata. **Compare with** picks another revision or the latest. **Restore this revision** makes it the library's latest again (borrowers keep theirs until updated). ## Find existing skills `/find-skills`, reached from the Library header or ⌘K, scans a folder for skill copies and adopts them, like `shelf scan` and `shelf adopt`. - The folder defaults to the one that holds your registered projects (for `~/code/a` and `~/code/b`, `~/code`), or your home directory. Depth is 1 to 10 levels, default 6. - Results group copies by skill and version, marked **in library**, **library latest**, **old library revision** and **managed**. - Tick unmanaged copies (or a whole group) and **Adopt N copies**. Within each skill, the library's or most common version is adopted first. - **These copies have no local edits (older versions)** is `shelf adopt --unedited`. - The result lists each copy as Imported, Matched, Older or Differs, with its loan and any audit findings. See [Migrating](https://limyuquan.github.io/shelf/docs/migrating.md). ## Activity `/activity` is a timeline of the latest 300 events: who did what, in which project ("codex used api-design in billing-api · 2m ago"). Actors read `you`, `you (dashboard)`, `claude-code`, `codex` and so on. Similar events by the same actor in the same project within a minute are merged into one line. ## Insights `/insights` shows what skills cost and how much they are used: - Totals: skills in the library, active loans, median session context per project, skills unused in 30 days. - **Context at session start**: a bar per project, split into skills loaded everywhere and borrowed skills; expand one for its skills. - **Skills active per day**: a 30-day chart; pick a day to see which skills were used. - **Skills**: every library skill with a 30-day sparkline, active days, last use, session and body tokens, and borrower count, sortable, with **never used** and **unused 30d** badges. - **Loaded everywhere**: the user-level skills in `~/.claude/skills`, `~/.agents/skills` and the other harness folders. See [Context budget](https://limyuquan.github.io/shelf/docs/context-budget.md). ## Settings `/settings` shows: - the shelf version, home and library paths; - **Agent hooks**: each harness's status (Installed, Not installed, Outdated, Settings unreadable, Not installed on this machine), with a reminder to trust Codex hooks in `/hooks`; - **Health**: the `shelf doctor` checks, with a **Repair** button (`shelf doctor --fix`) when something is wrong; - **Configuration**: every config key and its value, read-only. Edit `~/.shelf/config.json` to change them. ## Keyboard shortcuts Press `?` to see them in the app. | Key | Action | |---|---| | ⌘K / Ctrl+K | Search and commands | | `g` then `a` / `p` / `l` / `y` / `i` / `s` | Go to Attention / Projects / Library / Activity / Insights / Settings (within 1 second) | | `?` | Show shortcuts | | `j` / `k`, ↓ / ↑ | Next / previous row | | Enter | Open | | `r` | Renew (Attention, project page) | | `u` | Update to latest | | `c` | Review changes | | `x` | Select row | | Esc | Clear selection, then the highlighted row | | `b` | Borrow skills (project page) | | `/` | Filter (library) | | ⌘S / Ctrl+S | Save (editor) | Single-key shortcuts stand down while you type in a field, while a dialog or menu is open, and when ⌘, Ctrl or Alt is held. **⌘K** searches pages, projects ("Borrow skills into …" too), skills by name and description, and, from three characters, the content of skills (opening the file that matches). It also switches between light and dark themes. ## Live updates The dashboard updates by itself as agents and the CLI work. The server checks the database every second and watches the library folder, and pushes a change event to open pages, which refetch. The dot in the sidebar footer shows **Live**, **Connecting…** or **Reconnecting…**. The connection pauses while the tab is hidden and reconnects (with backoff, up to 30 seconds) when it returns, refetching everything in case it missed a change. ## Themes Dark, light, or following the system: choose in the sidebar footer's **Theme** menu (⌘K offers light and dark). The choice is kept in the browser. ## Security - The server binds 127.0.0.1 only. shelf has no remote-access mode. - Every API request needs the token, sent in a custom header. Other web pages can't send it (that would need CORS, which is never granted), which blocks cross-site requests. - Every API request must name the loopback address in its `Host` header (`127.0.0.1:` or `localhost:`), which blocks DNS rebinding. - The token lives in `~/.shelf/ui-token` (mode 0600), so bookmarks survive restarts. The app moves it from the URL into the browser's local storage and removes it from the address bar. - If the token is missing or wrong, the app shows a **Connect to shelf** screen: open the link printed by `shelf ui`, or paste the token from it. - `shelf ui --rotate-token` replaces the token and signs out every browser. - A second `shelf ui` on a port that is already in use fails with `CONFLICT` instead of sharing it. ## Phones and tablets The layout adapts to small screens: the sidebar becomes a drawer, detail panels stack under the content, dialogs become bottom sheets, and touch targets grow. Row actions and checkboxes are always visible on touch screens, since they can't hover. Because the server only listens on 127.0.0.1 and checks the `Host` header, a phone can't reach it directly. If you want it on another device, forward the same port to that device's loopback yourself (for example an SSH tunnel, `ssh -L 4222:127.0.0.1:4222 `) and open the printed URL there. shelf doesn't provide or configure remote access. ## Related - [`shelf ui`](cli/ui.md) - [Files](https://limyuquan.github.io/shelf/docs/files.md) (the token file) --- # Agents Source: https://limyuquan.github.io/shelf/docs/agents.md How coding agents use shelf on their own: how they learn about it, the JSON envelope and exit codes, how shelf knows which agent acted, status actions and session-start notes, and the rules agents follow. shelf is built to be operated by agents as much as by you. Every command has `--json`, never prompts, and is safe to retry. Errors carry a `hint` with the command that fixes them. ## How agents learn about shelf 1. **The bundled skill.** `shelf setup` installs a short `shelf` skill in your user-level skill folders, so agents in every project know shelf exists and when to use it. Its description costs about 66 tokens per session: ```markdown --- name: shelf description: Manage this project's agent skills from the user's personal skill library with the `shelf` CLI (borrow, renew, return, update, promote). Use when a `shelf:` note appears, when the user asks about skills, or when a task needs a skill the project does not have. --- Borrowed skills renew automatically when used and are returned after going unused. A `shelf:` note at session start means something needs attention: act on it. - Need a capability? `shelf catalog --json`, check it with `shelf show `, then `shelf borrow `. - Due soon but still needed: `shelf renew --reason ""`. No longer needed: `shelf return `. - Improved a borrowed skill? `shelf promote ` publishes it to the library. Library changed? `shelf update `. - Never hand-edit or delete skill copies to change them everywhere; shelf tracks them by hash. `shelf status --json` lists every loan with next steps. Errors include a `hint` with the fix. Full guide: `shelf guide`. ``` 2. **The guide.** `shelf guide` prints the full rules for agents (about 140 lines): loan states, choosing skills, due dates, keeping, changing skills everywhere, existing skills, imports and harness directories. It needs no shelf home or project, so it never fails. `shelf guide --json` wraps it in the envelope as `data.guide`. 3. **The session-start note.** In Claude Code and Codex, the [hooks](https://limyuquan.github.io/shelf/docs/hooks.md) add one `shelf: …` line to the agent's context when something needs attention. No note means nothing to do. 4. **These docs.** Every page is available as Markdown, and [llms.txt](https://limyuquan.github.io/shelf/llms.txt) indexes them for agents. ## The JSON envelope With `--json`, every command prints exactly one line on stdout: ```json {"schemaVersion":1,"ok":true,"data":{"skills":[{"skill":"pdf-tools","status":"borrowed","revision":"sha256:dca5f929e749b602869f964b944ab1c8567353094f413d0f53dabe7dfa015b67","dueAt":"2026-11-06T05:51:38.125Z","targets":[".agents/skills",".claude/skills"],"mode":"copy","kept":false}]}} ``` ```json {"schemaVersion":1,"ok":false,"error":{"code":"NOT_BORROWED","message":"\"nope\" is not borrowed by storefront","hint":"Run `shelf borrow nope` first"}} ``` - `schemaVersion` is `1`. It changes only when the envelope or a command's `data` shape changes incompatibly. - Dates are ISO 8601 strings in UTC. - `hint` is a string or `null`. - Usage errors (an unknown command, a missing argument) also arrive as an envelope with code `INVALID_ARGUMENT` when `--json` is given. Each command's page in the [CLI reference](https://limyuquan.github.io/shelf/docs/cli/index.md) shows its `data` shape. ## Exit codes | Exit | Error codes | |---|---| | 0 | Success | | 1 | `INTERNAL` (an unexpected error), and `shelf lint` when a skill has errors | | 2 | `INVALID_ARGUMENT`, `INVALID_SKILL` | | 3 | `NOT_INITIALIZED` | | 4 | `SKILL_NOT_FOUND`, `NOT_BORROWED` | | 5 | `SKILL_EXISTS`, `CONFLICT` | | 6 | `LOCAL_CHANGES` | | 7 | `LOAN_LIMIT` | | 8 | `NOT_ALLOWED` | Branch on `error.code` rather than the exit code; several codes share one exit code. See [Errors](https://limyuquan.github.io/shelf/docs/errors.md) for what each means and how to resolve it. ## Status and actions ```sh shelf status --json ``` | Field | Meaning | |---|---| | `data.initialized` | `false` if the project doesn't use shelf. Leave it alone unless the user asks to start using shelf there; there is deliberately no `shelf init` action. | | `data.loans[]` | Every loan: `skill`, `content`, `due`, `dueAt`, `daysLeft`, `lastUsedAt`, `kept`, `loanDays`, `policy`, `revision`, `latestRevision`, `targets`. | | `data.expired[]` | Skills this call just returned because they were overdue. | | `data.actions[]` | Next steps, most urgent first: `{ "command": "shelf update pdf-tools", "reason": "The library has a newer revision of pdf-tools" }`. | | `data.warnings[]` | Library problems and lockfile entries this machine's library doesn't have. | Each action's `command` is runnable as-is, except `` placeholders, which the agent replaces with a real reason: | Loan | Action | |---|---| | `missing` | `shelf sync` | | `diverged` | `shelf show ` (review, then promote with `--force` or update with `--force`) | | `modified` | `shelf promote ` (or detach, or update with `--force`) | | `behind`, pinned | `shelf update ` | | `behind`, follow | `shelf sync` | | overdue, no edits | `shelf sync` (returns it) | | overdue, edited | `shelf detach ` (or promote) | | `due-soon` | `shelf renew --reason ""` (or return it) | `shelf status` itself returns overdue loans without local edits, so it is not read-only. To look without changing anything, read the dashboard or `shelf projects`. ## Which agent acted The activity log records an actor for every change. shelf picks it in this order: 1. `--actor ` 2. `SHELF_ACTOR` 3. `CODEX_THREAD_ID` or `CODEX_SESSION_ID` set: `agent:codex` 4. `CLAUDECODE` set: `agent:claude-code` 5. `AI_AGENT` set: `agent:` plus its first word, lowercased (`claude-code_2-1-289_agent` becomes `agent:claude-code`) 6. A terminal on stdin: `user`; otherwise `unknown` Codex is checked before Claude Code because harnesses pass their environment on: a Codex session started from Claude Code sees both, and Codex is the one running shelf. Hooks record `agent:`. Actors starting with `agent:` may not import skills from git sources unless the user enables `allowAgentImports` (see [Importing skills](https://limyuquan.github.io/shelf/docs/importing-skills.md#what-agents-may-do)). ## The rules agents follow The guide gives agents these rules. They are the contract between you and your agents: - **Borrow only what the task needs.** Every skill costs context in every session. Check `shelf catalog`, `shelf search` or `shelf show` first. - **Renew with a reason, or return.** A `due-soon` skill has gone unused. `shelf renew --reason ""` if the project still needs it, else `shelf return `. The reason appears in the activity log. - **Record uses where there are no hooks.** `shelf used ` after using a skill. - **Keep only direct dependencies.** `shelf keep --reason ""` only for skills covering the framework, database or platform the project is built on (Convex skills in a project depending on `convex`). Never "just in case". - **Promote improvements.** An improved borrowed skill goes back with `shelf promote `; `--propagate` updates other projects, skipping copies with their own edits. - **Imports need the user.** Agents may run the review step of `shelf add` and `shelf pull`, and link existing skills to a source. Importing from a remote source with `--yes` is refused unless the user enabled it: show the user the review and the exact command. Never pass `--force` on high-severity findings without explicit approval. - **Only when asked:** `shelf init` in a new project, `shelf adopt`, `shelf restore`, `shelf rename`, `shelf duplicate`, `shelf archive`, `shelf set save` / `delete`, `shelf loan-days`, `shelf targets` changes, and editing library files under `~/.shelf`. - **Never edit the lockfile by hand, and never delete skill copies by hand.** Use `shelf return`; deleted copies count as `missing` and come back. ## Session-start notes The note is one line. Each item names the skills and the command to run: ``` shelf: returned after going unused: pdf-tools (`shelf borrow ` to get one back); library has updates: react-best-practices — `shelf update `. Details: `shelf status`. ``` | Item | Agent's move | |---|---| | returned after going unused | Borrow again only if the current task needs it. | | due soon unless used | Renew with a reason if still needed, else return. | | overdue, not returned because of local edits | Promote or detach. | | edited here | Promote, if the edits are improvements. | | edited here and in the library | Review with `shelf diff` before choosing. | | library has updates | `shelf update `. | ## llms.txt The docs site publishes [llms.txt](https://limyuquan.github.io/shelf/llms.txt): a short summary of shelf with the facts agents need most, and a list of every docs page as Markdown. [llms-full.txt](https://limyuquan.github.io/shelf/llms-full.txt) has every page in one file. Point an agent at either when it needs to know shelf beyond `shelf guide`. ## Related - [`shelf guide`](cli/guide.md), [`shelf status`](cli/status.md) - [Hooks](https://limyuquan.github.io/shelf/docs/hooks.md), [Errors](https://limyuquan.github.io/shelf/docs/errors.md), [Environment](https://limyuquan.github.io/shelf/docs/environment.md) - [Automation](https://limyuquan.github.io/shelf/docs/automation.md) --- # Automation Source: https://limyuquan.github.io/shelf/docs/automation.md How to run shelf unattended: a daily `shelf sweep` with cron, systemd or launchd, using shelf in CI, and scripting it with `--json`. ## Sweep every project daily Overdue loans are returned when someone runs `shelf status` or `shelf sync` in the project, or when a session starts there with hooks installed. Projects nobody opens keep their overdue skills until then. `shelf sweep` runs a sync in every registered project, so a daily sweep returns them everywhere: ```console $ shelf sweep Synced 3 project(s). billing-api: updated pdf-tools ``` It returns overdue loans without local edits, restores missing copies, and updates `--follow` loans in each project whose directory exists. Only projects where something changed get a line. Projects whose directory is gone are listed as ``docs-site: /home/me/code/docs-site is missing (`shelf doctor --fix`)``, and `shelf doctor --fix` forgets them. The schedulers below run shelf without a terminal, so the activity log records the actor as `unknown`. Set `SHELF_ACTOR` to name it. ### cron ```sh crontab -e ``` ```cron # Every day at 09:00 0 9 * * * SHELF_ACTOR=cron /home/me/.local/bin/shelf sweep >/dev/null 2>&1 ``` Use the absolute path of the binary: cron's `PATH` is minimal. With npm, `which shelf` gives the launcher, which needs `node` on cron's `PATH`; the binary itself is simpler. ### systemd (user timer) `~/.config/systemd/user/shelf-sweep.service`: ```ini [Unit] Description=Return overdue shelf loans in every project [Service] Type=oneshot Environment=SHELF_ACTOR=systemd ExecStart=%h/.local/bin/shelf sweep ``` `~/.config/systemd/user/shelf-sweep.timer`: ```ini [Unit] Description=Daily shelf sweep [Timer] OnCalendar=daily Persistent=true [Install] WantedBy=timers.target ``` ```sh systemctl --user daemon-reload systemctl --user enable --now shelf-sweep.timer ``` `Persistent=true` runs a missed sweep after the machine was off. ### launchd (macOS) `~/Library/LaunchAgents/com.github.limyuquan.shelf-sweep.plist`: ```xml Label com.github.limyuquan.shelf-sweep ProgramArguments /usr/local/bin/shelf sweep EnvironmentVariables SHELF_ACTOR launchd StartCalendarInterval Hour 9 Minute 0 ``` ```sh launchctl load ~/Library/LaunchAgents/com.github.limyuquan.shelf-sweep.plist ``` ## In CI shelf is a personal tool: the library and loans live on your machine, so CI can't borrow or renew. Two things are useful there. **Lint skills.** If you keep skills in a repository (for example a shared skills repo you `shelf add` from), lint them in CI with a scratch shelf home: ```sh export SHELF_HOME="$(mktemp -d)" mkdir -p "$SHELF_HOME/library" cp -R skills/* "$SHELF_HOME/library/" shelf lint # exits 1 on lint errors shelf audit --json | jq -e '[.data.skills[].findings[] | select(.severity == "high")] | length == 0' ``` `shelf lint` also reports skills that fail to load at all (no frontmatter, a missing description, a name that doesn't match the directory) as errors. `shelf audit` always exits 0, so the check above reads its JSON. **Check the lockfile.** `.agents/shelf.lock.json` is plain JSON with a fixed schema (see [Lockfile](https://limyuquan.github.io/shelf/docs/lockfile.md)), so CI can read it, for example to check that every skill it lists is also committed: ```sh jq -r '.skills | to_entries[] | .key as $n | .value.targets[] | "\(.)/\($n)/SKILL.md"' .agents/shelf.lock.json | xargs ls ``` Never let CI write the lockfile. ## Scripting with --json Every command except `shelf ui` returns once and prints one JSON line with `--json`, so `jq` works on it directly: ```sh # Skills due within a week in this project shelf status --json | jq -r '.data.loans[] | select(.due == "due-soon") | .skill' # Every project with overdue loans shelf projects --json | jq -r '.data.projects[] | select(.overdue > 0) | .path' # Library skills nobody has used shelf insights --json | jq -r '.data.skills[] | select(.neverUsed) | .name' # Fail when shelf is unhealthy shelf doctor --json | jq -e 'all(.data.checks[]; .status == "ok")' ``` Check `.ok` before reading `.data`, and branch on `.error.code` for failures; the exit code tells you only the class of error (see [Errors](https://limyuquan.github.io/shelf/docs/errors.md)). Commands never prompt and are safe to retry, and several can run at once: writes are serialised on the database. A script that runs inside an agent harness inherits its environment, so its actions are recorded as that agent. Pass `--actor` (or set `SHELF_ACTOR`) to name your script instead. `shelf ui --json` prints the URL envelope and keeps serving; read the first line and leave the process running. ## Related - [`shelf sweep`](cli/sweep.md), [`shelf doctor`](cli/doctor.md) - [Agents](https://limyuquan.github.io/shelf/docs/agents.md), [Environment](https://limyuquan.github.io/shelf/docs/environment.md) --- # CLI reference Source: https://limyuquan.github.io/shelf/docs/cli/index.md The `shelf` command line: global flags, the JSON envelope, output conventions, exit codes, how arguments are parsed, and every command with a link to its page. ## Usage ```sh shelf [arguments] [options] shelf --help shelf --version ``` Run shelf from anywhere inside a project for commands that act on "this project": shelf finds the project root by walking up to the nearest `.agents/shelf.lock.json`, else the nearest git root, else the working directory. Library commands work from anywhere. ## Global options Every command except `guide`, `ui` and `hook` accepts these: | Option | Effect | |---|---| | `--json` | Print one line: a JSON envelope `{ schemaVersion, ok, data \| error }`. | | `--actor ` | Who is acting, recorded in the activity log. Default: auto-detected (see [Environment](https://limyuquan.github.io/shelf/docs/environment.md#actor-detection)). | | `--help`, `-h` | Print the command's usage, arguments and options. | `shelf --version` prints the version (`0.4.0`). `shelf guide` and `shelf ui` accept `--json` but not `--actor`. ## The JSON envelope With `--json`, the command prints exactly one line on stdout, whether it succeeds or fails: ```json {"schemaVersion":1,"ok":true,"data":{"skill":"release-notes","previousDueAt":"2026-11-06T05:51:38.699Z","dueAt":"2026-12-06T05:51:38.699Z"}} ``` ```json {"schemaVersion":1,"ok":false,"error":{"code":"LOAN_LIMIT","message":"Due date 2027-06-24T05:34:17.582Z is beyond the 90-day loan limit","hint":"The latest allowed due date is 2027-01-05"}} ``` | Field | Type | Meaning | |---|---|---| | `schemaVersion` | number | `1`. Bumped only on a breaking change to the envelope or any command's `data`. | | `ok` | boolean | Whether the command succeeded. | | `data` | object | The result, when `ok` is `true`. Its shape is documented on each command's page. | | `error.code` | string | A stable error code, when `ok` is `false`. See [Errors](https://limyuquan.github.io/shelf/docs/errors.md). | | `error.message` | string | What went wrong, in words. | | `error.hint` | string or null | What to do about it, often an exact command. | Dates in `data` are ISO 8601 strings in UTC. Revisions are full hashes (`sha256:` plus 64 hex characters). Paths are absolute. With `--json`, usage errors (an unknown command, a missing argument) are envelopes too, with code `INVALID_ARGUMENT`: ```json {"schemaVersion":1,"ok":false,"error":{"code":"INVALID_ARGUMENT","message":"Missing required positional argument: SKILL","hint":"Run `shelf --help` or `shelf --help` for usage"}} ``` ## Human output Without `--json`, results go to stdout as plain text: tables with two-space columns, short hashes (10 hex characters), dates as `YYYY-MM-DD`. Errors go to stderr as `error: ` and `hint: `. An unexpected (internal) error also prints its stack trace. Text output is for people and may change between versions. Scripts and agents should use `--json`. ## Exit codes | Exit | Meaning | |---|---| | 0 | Success. | | 1 | `INTERNAL` error, or `shelf lint` found errors. | | 2 | `INVALID_ARGUMENT`, `INVALID_SKILL`, or a usage error (unknown command, missing argument). | | 3 | `NOT_INITIALIZED` | | 4 | `SKILL_NOT_FOUND`, `NOT_BORROWED` | | 5 | `SKILL_EXISTS`, `CONFLICT` | | 6 | `LOCAL_CHANGES` | | 7 | `LOAN_LIMIT` | | 8 | `NOT_ALLOWED` | `shelf hook` always exits 0. See [Errors](https://limyuquan.github.io/shelf/docs/errors.md) for every code. ## Conventions - **Never prompts.** Every command runs to completion without input, so agents and scripts can run it. - **Safe to retry.** Borrowing a borrowed skill, keeping a kept one, `init` in an initialized project and `setup` are all no-ops the second time. - **Refuses to lose work.** Anything that would discard local edits needs `--force`; importing needs `--yes`. - **Several names.** `borrow`, `keep`, `used`, `update`, `lint`, `audit` and `adopt` accept several names in one call. - **Comma-separated lists.** Options that take lists (`--skill`, `--project`, `--add`, `--remove`) split on commas. - **Negative due shifts.** Arguments starting with `-` are read as options, except negative shifts for `shelf due`: `shelf due api-design -7d` needs no `--`. - **Concurrency.** Several shelf processes can run at once, in the same project or not. Writes are serialised on the database and the lockfile is rewritten under the same lock. ## Commands ### Getting started | Command | Description | | --- | --- | | [`shelf setup`](setup.md) | Create the shelf home, install the shelf skill and the hooks that renew skills on use (safe to re-run) | | [`shelf init`](init.md) | Register the current project with shelf (creates .agents/shelf.lock.json) | | [`shelf status`](status.md) | Show this project's loans and suggested next steps (returns overdue skills) | | [`shelf guide`](guide.md) | Print the full guide for agents | ### Library | Command | Description | | --- | --- | | [`shelf new`](new.md) | Create a skill in your library | | [`shelf catalog`](catalog.md) | List library skills, optionally filtered by search terms | | [`shelf search`](search.md) | Find library skills by what they say: names, descriptions, SKILL.md and references | | [`shelf show`](show.md) | Print a library skill's SKILL.md and file list | | [`shelf log`](log.md) | Show a skill's revisions and which projects borrow each | | [`shelf diff`](diff.md) | Diff two versions of a skill: borrowed, library, project, or a revision | | [`shelf propagate`](propagate.md) | Push the library's latest revision of a skill to every borrowing project | | [`shelf restore`](restore.md) | Make an earlier revision of a skill the library's latest again | | [`shelf loan-days`](loan-days.md) | Show or set a skill's loan length (how long loans and renewals last) | | [`shelf set`](set.md) | Group library skills into sets, borrowed together with `shelf borrow @` | | [`shelf set list`](set.md) | List your skill sets | | [`shelf set save`](set.md) | Create a set, or replace its skills (accepts @set to extend another set) | | [`shelf set delete`](set.md) | Delete a set (borrowed skills and loans are unaffected) | | [`shelf rename`](rename.md) | Rename a library skill (its directory and frontmatter name), keeping its history | | [`shelf duplicate`](duplicate.md) | Copy a library skill to a new name, as a new skill with its own history | | [`shelf archive`](archive.md) | Move a library skill to the archive (nothing is deleted) | | [`shelf lint`](lint.md) | Check skills' SKILL.md against the Agent Skills format (exits 1 on errors) | ### Loans in the current project | Command | Description | | --- | --- | | [`shelf insights`](insights.md) | Context each project loads at session start, and which skills agents actually use (30 days) | | [`shelf suggest`](suggest.md) | Suggest library skills that match what this project uses (its dependencies, files) | | [`shelf borrow`](borrow.md) | Copy library skills into this project with a due date | | [`shelf renew`](renew.md) | Renew a loan: due the loan length (or --days) from today, unless already due later | | [`shelf used`](used.md) | Record that borrowed skills were used, which renews them (hooks do this for you) | | [`shelf due`](due.md) | Move a loan's due date: +14d, -7d, +2w or 2026-12-01 | | [`shelf keep`](keep.md) | Keep borrowed skills: they never expire (--off to stop keeping) | | [`shelf return`](return.md) | Remove a borrowed skill from this project | | [`shelf update`](update.md) | Update borrowed skills to the library's latest revision (all if none named) | | [`shelf promote`](promote.md) | Publish this project's edits to a skill back to the library | | [`shelf detach`](detach.md) | Stop managing a skill; its files stay in the project | | [`shelf sync`](sync.md) | Return overdue skills, restore missing copies, apply updates to --follow loans | | [`shelf targets`](targets.md) | Show or change which harness skill directories this project uses | ### Bringing skills in: existing copies, or from outside | Command | Description | | --- | --- | | [`shelf scan`](scan.md) | Find skill copies under a directory and group duplicates and drifted versions | | [`shelf adopt`](adopt.md) | Import existing skill directories into the library and manage them as loans | | [`shelf add`](add.md) | Import skills from a git repository or directory (reviews first; --yes imports) | | [`shelf pull`](pull.md) | Update an imported skill from its source (shows diff and audit; --yes applies) | | [`shelf audit`](audit.md) | Scan library skills for risky content (all, or the named ones) | ### Across projects | Command | Description | | --- | --- | | [`shelf projects`](projects.md) | List every project using shelf, with loan counts | | [`shelf sweep`](sweep.md) | Run `sync` in every registered project (e.g. from a daily cron job) | | [`shelf doctor`](doctor.md) | Check shelf's state for problems; --fix repairs what it safely can | | [`shelf ui`](ui.md) | Open the local dashboard (projects, loans, library editor) | ### Called by harness hooks, not people | Command | Description | | --- | --- | | [`shelf hook`](hook.md) | Run a harness hook (installed by `shelf setup`; reads the payload on stdin) | --- # Configuration Source: https://limyuquan.github.io/shelf/docs/configuration.md shelf's config file, `~/.shelf/config.json`: where it lives, every key with its default, how to change it, and what happens when it is invalid. Project-specific settings live in the project's lockfile instead. ## The file The config is `config.json` in the shelf home: `~/.shelf/config.json`, or `$SHELF_HOME/config.json` when `SHELF_HOME` is set. `shelf setup` creates it with every default: ```json { "loanDays": 30, "maxLoanDays": 90, "dueSoonDays": 7, "allowAgentImports": false, "hooks": true, "mode": "copy", "targets": [ ".agents/skills", ".claude/skills" ] } ``` Edit it with any editor; there is no `shelf config` command. Every command reads it when it starts. `shelf ui` reads it once at startup, so restart the dashboard after editing it. Missing keys take their defaults, so a file with only the keys you change works too. Without a file, every default applies. `shelf setup` only ever changes the `hooks` key in an existing file; everything else you set is kept. ## Keys | Key | Type | Default | Description | | --- | --- | --- | --- | | `loanDays` | integer | `30` | Loan length in days for `borrow`, and the default extension for `renew`. | | `maxLoanDays` | integer | `90` | Upper bound on how far in the future a due date may be set, in days. | | `dueSoonDays` | integer | `7` | Loans due within this many days are reported as `due-soon`. | | `allowAgentImports` | boolean | `false` | Lets agents run `shelf add` / `shelf pull` from remote sources. Off by default: the library is the trust boundary, and only the user should widen it. | | `hooks` | boolean | `true` | Install harness hooks (Claude Code, Codex) that renew loans when a skill is used and report loans needing attention at session start. Set by `shelf setup`. | | `mode` | `"copy"` \| `"link"` | `"copy"` | `copy`: a copy per target. `link`: one copy, other targets symlink to it. | | `targets` | string[] | `[".agents/skills",".claude/skills"]` | Project-relative directories that borrowed skills are written into. | ### loanDays The default loan length, in days: how long a new loan lasts, and how far a use or a renewal moves the due date. A skill can override it with [`shelf loan-days`](cli/loan-days.md). Always capped at `maxLoanDays`. ### maxLoanDays No command sets a due date more than this many days from now (`borrow --days`, `renew`, `due`, `loan-days` all fail with `LOAN_LIMIT` past it). Lowering it doesn't shorten existing loans, but skill loan lengths above it are capped from then on. ### dueSoonDays A loan is `due-soon` when it is due within this many days. It then shows in `shelf status` next steps, the session-start note and the dashboard's Attention page. `0` turns the state off. ### allowAgentImports When `false`, agents (actors starting with `agent:`) may review skills from git sources but not import them: `shelf add --yes` of a new skill and `shelf pull --yes` fail with `NOT_ALLOWED`. Set it to `true` only if you want agents to widen your library on their own. See [Importing skills](https://limyuquan.github.io/shelf/docs/importing-skills.md#what-agents-may-do). ### hooks Whether shelf's harness hooks should be installed. `shelf setup` sets it (`--no-hooks` writes `false`), and `shelf doctor` checks the hooks only when it is `true`. Editing it by hand doesn't install or remove hooks; run `shelf setup` or `shelf setup --no-hooks`. ### mode `copy` gives every target its own copy of a borrowed skill. `link` keeps one copy in the first target and makes the others relative symlinks to it (junctions on Windows). Applies to new loans; `shelf borrow --link` chooses link mode for one loan. See [Harnesses](https://limyuquan.github.io/shelf/docs/harnesses.md#link-mode). ### targets The project-relative directories borrowed skills are written into, at least one. A project can override it with [`shelf targets`](cli/targets.md), which stores its own list in the lockfile. Changing this key doesn't move existing loans; `shelf targets --reset` in a project applies it there. ## Examples Longer loans, warned about two weeks ahead: ```json { "loanDays": 60, "dueSoonDays": 14 } ``` One copy per project, also written for Kiro: ```json { "mode": "link", "targets": [".agents/skills", ".claude/skills", ".kiro/skills"] } ``` ## Invalid config A config that isn't valid JSON, or has a value of the wrong type, makes every command fail with `INVALID_ARGUMENT` (exit 2) until it is fixed: ```console $ shelf status error: Invalid config /home/me/.shelf/config.json: ✖ Invalid input: expected number, received string → at loanDays ``` Unknown keys are ignored. ## Settings that aren't in the config | Setting | Where | Command | |---|---|---| | A skill's loan length | Database, per machine | `shelf loan-days` | | A project's targets | Lockfile, shared with clones | `shelf targets` | | A loan's policy, mode and keep | Database; mode and keep also in the lockfile | `shelf borrow --follow --link`, `shelf keep` | | Sets | Database, per machine | `shelf set` | | Where the shelf home is | `SHELF_HOME` | See [Environment](https://limyuquan.github.io/shelf/docs/environment.md) | ## Related - [Environment](https://limyuquan.github.io/shelf/docs/environment.md), [Files](https://limyuquan.github.io/shelf/docs/files.md), [Lockfile](https://limyuquan.github.io/shelf/docs/lockfile.md) --- # Errors Source: https://limyuquan.github.io/shelf/docs/errors.md Every error code shelf returns, its exit code, what causes it, and what to do about it. Codes are stable: agents and scripts should branch on `error.code`, and read `error.hint` for the exact fix. ## How errors look Without `--json`, on stderr: ```console $ shelf return pdf-tools error: The project copy of "pdf-tools" has local edits hint: Keep them with `shelf promote pdf-tools` or `shelf detach pdf-tools`, or discard them with --force ``` With `--json`, one line on stdout: ```json {"schemaVersion":1,"ok":false,"error":{"code":"NOT_BORROWED","message":"\"nope\" is not borrowed by storefront","hint":"Run `shelf borrow nope` first"}} ``` `hint` is `null` when there is nothing more specific to say than the message. ## Codes | Code | Exit code | Meaning | | --- | --- | --- | | `INTERNAL` | 1 | Anything that is not a ShelfError: a bug or an unexpected I/O error. The message says what failed. | | `INVALID_ARGUMENT` | 2 | A missing or malformed argument, option or config value, or a request that does not apply (for example promoting a copy with no local edits). | | `INVALID_SKILL` | 2 | A SKILL.md is missing, has no or invalid YAML frontmatter, or breaks a rule of the Agent Skills spec (name, description). | | `NOT_INITIALIZED` | 3 | The current directory is not in a shelf project. Run `shelf init` in the project root. | | `SKILL_NOT_FOUND` | 4 | No skill, set or source entry by that name. | | `NOT_BORROWED` | 4 | The skill is in the library, but this project has not borrowed it. | | `SKILL_EXISTS` | 5 | A skill or set with that name already exists. | | `CONFLICT` | 5 | The operation would overwrite or orphan something: files shelf does not manage, a newer library revision, copies edited differently, or loans in other projects. | | `LOCAL_CHANGES` | 6 | The project copy has local edits that the operation would discard. Promote or detach them first, or pass `--force`. | | `LOAN_LIMIT` | 7 | The requested loan length or due date is beyond `maxLoanDays`. | | `NOT_ALLOWED` | 8 | The actor may not do this, for example an agent importing from a remote source while `allowAgentImports` is off. | `INTERNAL` (exit 1) is not a shelf error code but what the CLI reports for anything unexpected: a bug, a full disk. The message is the underlying error; text output adds a stack trace. Please report reproducible ones. ## What to do ### INVALID_ARGUMENT An argument, option or file shelf reads is not acceptable. Read the message; it names the value. | Cause | Fix | |---|---| | Unknown command, missing argument | `shelf --help` | | An invalid skill or set name | 1 to 64 lowercase letters, digits and single hyphens | | A number option (`--days`, `--limit`, `--depth`, `--port`) that isn't a positive integer | Pass a whole number above 0 | | A due expression that isn't `+Nd`, `-Nd`, `+Nw`, `-Nw` or `YYYY-MM-DD` | Use one of those | | A revision that is too short, ambiguous or unknown | At least 6 hex characters; `shelf log ` lists them | | `shelf promote` on a copy without edits | Nothing to promote | | `shelf pull` on a skill without a source | Link it first with `shelf add --yes` | | `shelf add`: no SKILL.md, several skills without `--skill`/`--all`, a failed clone | Follow the hint | | `shelf targets`: a path outside the project, or no targets left | Use a harness id or a project-relative directory | | `shelf adopt` on a path inside the shelf home | It is already managed | | `shelf new` with a description over 1024 characters | Shorten it; nothing was created | | An invalid `config.json` | Fix the file; the message names the key | ### INVALID_SKILL A SKILL.md isn't a valid skill: no frontmatter, invalid YAML, a `name` that doesn't match its directory, a missing or overlong description. It comes from commands that read a skill directory you point at (`adopt`, `add`, `promote`, `restore`, `rename`). Fix the frontmatter; `shelf lint` and [Writing skills](https://limyuquan.github.io/shelf/docs/writing-skills.md) list the rules. ### NOT_INITIALIZED The working directory isn't inside a shelf project, and the command needs one (`borrow`, `renew`, `return`, `sync`, `targets`, `suggest`, …). Run the command inside the project, or `shelf init` in its root if it should start using shelf. Agents shouldn't run `init` unless asked. ### SKILL_NOT_FOUND No skill (or set) by that name. `shelf catalog` lists skills, `shelf set list` lists sets. Archived skills and library directories that aren't valid skills (see `shelf doctor`) count as not found, except to `shelf lint`, which reports why they don't load. `shelf add --skill` uses it for a name the source doesn't have; the hint lists what it has. ### NOT_BORROWED The project doesn't borrow that skill. `shelf status` lists its loans; `shelf borrow ` borrows it. `shelf diff` uses it when `borrowed` or `project` is asked for outside a project that borrows the skill. ### SKILL_EXISTS The name is taken: a library directory with that name exists (`new`, `rename`, `duplicate`), an archived skill had that name (`new`, `rename`, `duplicate`, `add`), or the skill is already linked to a source (`add`; use `shelf pull`). Choose another name, or restore the archived skill by moving it back from `~/.shelf/archive/`. ### CONFLICT The operation would clash with state shelf doesn't own or can't reconcile on its own. | Cause | Fix | |---|---| | `borrow`: a target already has an unmanaged directory with the skill's name | Adopt it (`shelf adopt `), or move it away | | `promote`: the library changed since the project borrowed (`diverged`) | Review with `shelf diff`, then `--force` to replace the library's revision | | `promote`: the copies in different targets were edited differently | Make them identical | | `promote` or `diff --to project`: copies are missing | `shelf sync` | | `pull`: the library copy was edited since the last import | `--force` to replace your edits with the source | | `rename`, `archive`: the skill is borrowed | Return it from each borrowing project first | | The lockfile isn't valid JSON or doesn't match its schema | Restore it from version control; never edit it by hand. See [Lockfile](https://limyuquan.github.io/shelf/docs/lockfile.md) | | A revision's snapshot is missing from the object store | `shelf doctor` | | `ui --port`: the port is in use | If it is another `shelf ui`, open the URL in the hint; otherwise pick another `--port`, or leave it out for a free one | ### LOCAL_CHANGES A project copy has local edits that the operation would discard: `return`, `update `, or `targets --remove`. Keep the edits with `shelf promote ` (to the library) or `shelf detach ` (in the project), or discard them with `--force`. ### LOAN_LIMIT The due date would be more than `maxLoanDays` (90 by default) from now: `borrow --days`, `renew`, `due` or `loan-days`. The hint gives the latest allowed date or number of days. Use a shorter period, or raise `maxLoanDays` in the [config](https://limyuquan.github.io/shelf/docs/configuration.md). For a skill the project always needs, `shelf keep` is the alternative. ### NOT_ALLOWED An agent tried to import a skill from a git source (`shelf add --yes` of a new skill, or `shelf pull --yes`) while `allowAgentImports` is `false`. The agent should show you the review and the exact command, for you to run. See [Importing skills](https://limyuquan.github.io/shelf/docs/importing-skills.md#what-agents-may-do). ## Usage errors without --json Without `--json`, an unknown command or a missing argument prints the command's help followed by the problem, and exits with code 2 (as with `--json`): ```console $ shelf new nod … Missing required argument: --description ``` ## Related - [CLI reference](https://limyuquan.github.io/shelf/docs/cli/index.md#exit-codes), [Troubleshooting](https://limyuquan.github.io/shelf/docs/troubleshooting.md) --- # Lockfile Source: https://limyuquan.github.io/shelf/docs/lockfile.md The format of `.agents/shelf.lock.json`, field by field: which skills shelf manages in a project, at which revision, in which directories, and whether they are kept. Why it has no timestamps, how clones use it, and why you should never edit it by hand. ## Example ```json { "version": 1, "project": "da6c4e70-e25d-4d10-a389-b6ed7360fa5a", "targets": [ ".agents/skills", ".claude/skills", ".kiro/skills" ], "skills": { "api-design": { "revision": "sha256:38faca9ff6bb0a09bdcf9b91c1671afa782b4962d317ffbfa4d2aa0ec9212ab7", "targets": [ ".agents/skills", ".claude/skills" ], "keep": true }, "pdf-tools": { "revision": "sha256:ddad27dd34082032a0f9564e301c57903aeb482c1f97b56b07844301a222bc36", "targets": [ ".agents/skills", ".claude/skills" ], "mode": "link" } } } ``` ## Fields | Field | Type | Meaning | |---|---|---| | `version` | `1` | The format version. | | `project` | UUID | The project's id. It identifies the project across moves and clones. | | `targets` | string array, optional | The project's own target directories, set by `shelf targets`. Omitted when the project uses your default targets from the config. | | `skills` | object | One entry per managed skill, keyed by skill name, sorted by name. | | `skills..revision` | string | The revision the project holds: `sha256:` and 64 hex characters, the content hash of the skill directory. | | `skills..targets` | string array | The directories this skill is copied into, relative to the project root. At least one. | | `skills..mode` | `"link"`, optional | Present for link-mode loans (one copy, symlinks elsewhere). Omitted for the default, copy. | | `skills..keep` | `true`, optional | Present for kept loans, which never expire. Omitted otherwise. | The file is JSON with two-space indentation and a trailing newline. It lives in `.agents/` whatever the project's targets are. ## Why no timestamps Due dates, last uses and the activity log live in the database on your machine, not in the lockfile. Using or renewing a skill therefore never changes the file, so committing it causes no churn and no merge conflicts between branches. It changes only when what the project holds changes: a borrow, return, update, promote, keep, detach or targets change. ## What it is for - **Recognising the project.** Commands walk up from the working directory to the nearest lockfile to find the project root. The `project` id lets a clone, or a moved directory, be recognised as the same project. - **Sharing decisions.** Kept skills and project targets are project decisions, so they travel with the repository: a clone keeps the same skills and writes to the same directories. - **Clones and other machines.** When shelf opens a project whose lockfile lists a skill it has no loan for, it creates the loan (pinned, with a fresh loan period, keep and mode from the lockfile). If the copies aren't there, the loan is `missing` and `shelf sync` (or the next session start) restores them, from the recorded revision if this machine has it, else from the library's latest. - **Entries this machine doesn't know.** A skill that isn't in this machine's library is kept in the lockfile untouched, and commands warn: `Lockfile lists "x", which is not in this machine's library`. A machine never deletes another machine's entries. ## How it is written shelf rewrites the whole file from the project's active loans after every change, inside the same database transaction, by writing a temporary file and renaming it into place. Several shelf processes in one project therefore never lose each other's entries. ## Commit it Commit `.agents/shelf.lock.json`. Whether to also commit the skill copies is up to you: - **Committed copies** work for everyone who clones the repository, with or without shelf. shelf tracks them by hash, so an edit shows up as `modified`. - **Ignored copies** keep the repository smaller. Anyone with shelf restores them with `shelf sync` after cloning, provided their library has the skills. ## Never edit it by hand The lockfile is derived from the database and rewritten on every change, so hand edits are overwritten or, if they break the schema, make every command in the project fail with `CONFLICT` until it is fixed. Use the commands instead: | To | Run | |---|---| | Add a skill | `shelf borrow ` | | Remove a skill | `shelf return ` or `shelf detach ` | | Change a revision | `shelf update `, `shelf promote ` | | Keep or stop keeping | `shelf keep `, `shelf keep --off` | | Change directories | `shelf targets --add/--remove/--reset` | If a merge leaves conflict markers in it, resolve it to valid JSON by taking either side. On your machine the database is the source of truth for loans: the next change (a borrow, return, update, …) rewrites the file from them. The exception is `keep`, which shelf takes from the lockfile whenever it opens the project, and lockfile entries without a loan, which become loans. ## Related - [Concepts](https://limyuquan.github.io/shelf/docs/concepts.md#the-lockfile), [Files](https://limyuquan.github.io/shelf/docs/files.md) - [`shelf init`](cli/init.md), [`shelf targets`](cli/targets.md), [`shelf keep`](cli/keep.md) --- # Files Source: https://limyuquan.github.io/shelf/docs/files.md Everything shelf reads and writes: the shelf home (`~/.shelf`), the files in your home directory and harness configs, the files in each project, and temporary files. Useful for backups, uninstalling, and knowing what to commit. ## The shelf home `~/.shelf`, or `$SHELF_HOME` when set (see [Environment](https://limyuquan.github.io/shelf/docs/environment.md)). ``` ~/.shelf/ config.json your settings shelf.db projects, loans, due dates, sets, sources, activity shelf.db-wal, shelf.db-shm SQLite's write-ahead log (while in use) library// the editable copy of each skill objects// an immutable snapshot of every revision archive/-