# 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.

- Skill: `ranveersingh1113/shelf-save` (Agent Skill)
- Install (CLI): `npx skillmds@latest add ranveersingh1113/shelf-save`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ranveersingh1113/shelf-save/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Docs & Writing
- License: MIT
- Author: Ranveersingh1113 (https://skillmd.com/u/ranveersingh1113)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/ranveersingh1113/shelf-save

---


# 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:

1. Ask: *"Where should your tool library live? Give me a folder path — an
   Obsidian vault subfolder works, or any normal folder."*
2. Write their answer as a single line to `~/.tool-library-path`.
   Use forward slashes even on Windows.
3. 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:

```markdown
<!--
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.

1. **Read** `<LIB>/inbox.md` and collect two kinds of item, ignoring the comment
   header and anything under the `## Failed` heading:
   - **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.
2. **List** `<LIB>/upload_documents/` if it exists. Every file there is an item.
3. **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`, `cid` and
   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.md` and in the
   note's `url:` field, so the archive stays comparable over time.
4. **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.
5. **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`.
6. **Append** each success to the `_processed.md` table as `| YYYY-MM-DD | x |`,
   where `x` is the URL, `(pasted) <title>`, or `(file) <filename>`.
7. **Clear `inbox.md` — carefully. This is the one destructive step in the run.**

   **Re-read `inbox.md` in 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.md` fails 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.md` has
   their filenames. Tell the user they can clear the folder themselves whenever
   they like.
8. **Regenerate** `_index.md` — read every note's frontmatter, group by
   `category`, one line each: `- [[note-name]] — <problem_solved>`.
9. **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`:

```markdown
---
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, set `confidence: 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.md` under 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.

