Shelf save
Save links, articles, documents, or screenshots into the user's tool library as searchable notes. USE THIS whenever the user says "process my inbox", "save this", "save these links", "ingest my newsletter links", "add to my tool library", "/shelf-save", "file these", "keep this for later" — or pastes a batch of URLs, pastes article or newsletter text, or hands over a PDF, doc, or screenshot with any intent to keep it. A URL is only the common case; pasted text and uploaded files are first-class inputs, and are the right answer whenever a link is paywalled, login-walled, or a tracking redirect. Also use when the user mentions their inbox.md, their tool library, or newsletter links piling up. Writes one note per thing, dedupes, rebuilds the index.From its SKILL.md
npx -y skills add Ranveersingh1113/shelf-skills --skill shelf-saveAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- 22 days oldThe repository was created 22 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its file declares
Copied from the file, not written here
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
12.4 KB, ~2.9k tokens by cl100k_base, as published. Nobody here has run it
shelf-save
Turn raw URLs into a searchable tool library of markdown notes.
Tools are named by what they do, with the Claude Code name in brackets. Use whatever your harness calls the equivalent.
Step 0 — find the library (always do this first)
Read ~/.tool-library-path (in the user's home directory). It holds one line:
the library folder. Call it <LIB> below.
If it exists, use that path.
If it's missing, this is a first run:
- Ask: "Where should your tool library live? Give me a folder path — an Obsidian vault subfolder works, or any normal folder."
- Write their answer as a single line to
~/.tool-library-path. Use forward slashes even on Windows. - Create this structure under it, then tell the user the full path you created and what goes where — they'll need to find it without you.
<LIB>/
├── inbox.md # user dumps URLs and pasted text here
├── upload_documents/ # user drops PDFs, docs, screenshots here
├── _processed.md # append-only archive; dedupe source of truth
├── _index.md # auto-generated, grouped by category
└── notes/ # one file per tool
_processed.md starts with a | date | source | table header. inbox.md
starts with this comment, verbatim:
<!--
Dump here. No formatting, no thinking.
URLs one per line
Articles paste the text, as messy as you like
Documents drop the file in the upload_documents/ folder next to this file
Then say "process my inbox".
Paste the text whenever a link won't load — paywalls, logins, newsletter
tracking redirects. It makes a better note than fetching would.
Everything that works becomes a note and leaves this file. Anything that
fails stays behind under a "Failed" heading.
-->
Never create or touch anything outside <LIB>.
Inputs — a URL is only the common case
Anything that carries the content works. Same note schema, same folder, every time. Never turn one of these away or ask the user to find a link instead.
| Input | What to do |
|---|---|
| URL | Fetch it [WebFetch]. The default path. |
| Pasted text — an article, an email, a newsletter body, release notes | Read it as given. Do not fetch anything. Set source: paste. |
| A file — PDF, docx, markdown, txt, a saved page | Read the file directly. Set source: file and record the filename in the Notes section. Read the whole document, not the first page. |
| A screenshot or image | Read the image. Set source: screenshot. If text in it is unreadable, say which parts you couldn't make out rather than guessing. |
Each arrives one of two ways — in chat ("save this: …", or handing over a file path), or dropped in the library for later, which is the point of the inbox and needs no session open:
- Text → pasted straight into
inbox.md, no syntax required. - Files → dropped into
<LIB>/upload_documents/. Any format. Filename doesn't matter.
confidence: high for all of these — you saw the real content, which is a
stronger source than a fetched page, not a weaker one.
Paste beats a dead link. Many URLs can't be fetched: newsletter tracking redirects, paywalls, login walls, JS-only pages, PDFs behind a CDN. When a link fails, tell the user the fix is to open it themselves and paste the text or drop the file in. That's a first-class path, not a workaround.
A document may hold several things worth saving. A conference PDF or a
newsletter issue can cover five tools. Write one note per thing, not one note
per file. If it's really a single work — a paper, a spec, a long essay — that's
one note, category: reading or research.
Longer documents get more room. The schema is a floor, not a ceiling. For a
paper or a spec, keep the frontmatter exactly as specified, then write as much
in the body as the thing deserves — arguments, findings, numbers, caveats. A
## Key points section is a good addition here. The problem_solved rule never
relaxes: still one sentence, still the user's problem.
Run
If the user handed you content directly (paste, file, or image), skip to
step 5 — build the note from what they gave you. Steps 1–4 are for the inbox.
Still do steps 6–9 so it's archived and indexed like anything else. For a paste
or file with no URL, put unknown in the url: field.
-
Read
<LIB>/inbox.mdand collect two kinds of item, ignoring the comment header and anything under the## Failedheading:- URLs — any line starting with
http, on its own line. - Pasted text — everything else. Use judgment about where one item
ends and the next begins. Do not split on blank lines — a real pasted
article is full of them, and splitting on them would shred one article into
dozens of fake notes. Read the content: one coherent piece of writing is
one item, however long. Two obviously unrelated pieces are two items. A
---on its own line, if the user bothers to add one, is an explicit separator you should honour. - URLs inside a pasted article are part of that article, not separate items. Only treat a URL as its own item when it stands alone.
- URLs — any line starting with
-
List
<LIB>/upload_documents/if it exists. Every file there is an item. -
Read
<LIB>/_processed.md. Drop anything already listed — by URL, by(pasted) <title>, or by filename. Count the drops.Compare URLs with tracking parameters stripped, not as literal strings. Newsletter links arrive carrying
utm_*,ref,dub_id,mid,cidand similar, and the same article forwarded twice will have different ones — a literal match sees two URLs and writes two notes for one thing. Strip the tracking params, drop any trailing#fragment, ignore a trailing slash, and compare what's left. Store the clean URL in_processed.mdand in the note'surl:field, so the archive stays comparable over time. -
Announce what you found, broken down by kind: "Found 3 links, 2 pasted items, 1 file (4 already processed). Processing now." The breakdown matters — it's the user's chance to catch a stray note-to-self that got read as an item, before any notes are written. Then go. Do not ask for per-item approval.
-
Process in batches of 5. For each item:
- URL → fetch the page [
WebFetch]. Pasted text → read it as given. File → read the file. - Extract into the schema below.
- List
<LIB>/notes/*.md[Glob] and check for a near-duplicate by title. If one exists, update it instead of creating a second file, and say so in the summary. - Write
<LIB>/notes/<kebab-case-name>.md.
- URL → fetch the page [
-
Append each success to the
_processed.mdtable as| YYYY-MM-DD | x |, wherexis the URL,(pasted) <title>, or(file) <filename>. -
Clear
inbox.md— carefully. This is the one destructive step in the run.Re-read
inbox.mdin full immediately before writing it. The user may have pasted more into it while you were processing — runs take minutes, and dumping into the inbox is a thing they do without thinking. Then write back: the header, anything in the re-read that you did not just process, and the failures under## Failed — retry or add manually. A clean run with nothing newly added leaves it empty apart from the header.If a write to
inbox.mdfails with a "file has been modified" error, that is not a stale handle — it means the user just edited the file. Re-read the whole file, never a truncated view, and diff it against what you collected in step 1 before writing anything. Content you never saw is content you are about to destroy.Processed files in
<LIB>/upload_documents/stay where they are — never delete a source file. They're skipped on the next run because_processed.mdhas their filenames. Tell the user they can clear the folder themselves whenever they like. -
Regenerate
_index.md— read every note's frontmatter, group bycategory, one line each:- [[note-name]] — <problem_solved>. -
Summarize: saved / updated / skipped-duplicate / failed. Flag every note that landed in
category: other— those need a human decision.
Note schema
Exactly this, at <LIB>/notes/<kebab-case-name>.md:
---
title: Resend
url: https://resend.com
source: inbox
date_saved: 2026-07-22
category: dev-tool
problem_solved: Sending transactional email from an app without deliverability headaches
pricing: Free tier 3k emails/mo; paid from $20/mo
maturity: stable
confidence: high
tags: [email, api, backend, saas]
---
## What it is
Two to three sentences, plain language. What it actually does.
## Use this when
- Concrete situation 1
- Concrete situation 2
## Don't use this when
- Concrete situation where it's the wrong choice
## Notes
Anything else worth remembering — alternatives, gotchas, integrations.
problem_solved — get this right, nothing else matters as much
This is the field shelf-find matches on. A bad one makes the note invisible
forever.
Write the user's problem, not the product's feature list. Phrase it as the situation they'd be in, in their words, not the vendor's marketing.
- Good:
Sending transactional email without fighting deliverability - Bad:
A modern email API for developers— that answers "what is this". Retrieval asks "what do I need right now".
One sentence. No product name in it.
Other fields
| field | rule |
|---|---|
category | exactly one of dev-tool ai-agent marketing design data fundraising ops research reading. No fit → other, and flag it in the summary. This list is the user's to change — it's a starting set, not a fixed taxonomy. If they want a category added or removed, edit it here and in shelf-find; recall goes blind to any category only one file knows about. |
source | inbox | paste | file | screenshot — where the content came from. |
url | the source URL. unknown for a paste or file with no link. |
maturity | experimental | stable | enterprise |
confidence | high if fetched and parsed clean. low if it came from fallback search, paywall, or JS-blocked page. |
tags | 3–6 lowercase keywords. Reuse existing tags over inventing near-synonyms — auth/authentication/authn as three tags means three partial matches instead of one good one. Check first by searching the notes for existing tags: lines. |
pricing | write unknown if not findable. Do not guess. |
Never invent facts. Any field you can't determine: unknown.
Failures
- Paywall / 403 / JS-only: fall back to a web search [
WebSearch] on the page title or domain. Build the note from that, setconfidence: low, and put this at the top of the note body:> [!warning] Fetched indirectly — verify before relying on this. - Total failure (search turns up nothing usable): leave the URL in
inbox.mdunder the failed heading. Never write a hallucinated note. When reporting failures, tell the user they can open the link themselves and paste the text, or drop the file in — that always works and produces a better note than a fetch would have. Newsletter tracking redirects and CDN-hosted PDFs fail this way constantly; they are not worth retrying. - Never delete a note. Never clear
_processed.md. - Note already exists and you're updating it: preserve the original
date_saved, overwrite the rest.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.