Find papers, books, and references in the user's Zotero library by topic, author, or title. Use this skill whenever the user wants to find, locate, or discover items in their research library. Triggers on: "find papers about X", "what do I have on X", "search my library", "which paper covers X", "do I have anything by [author]", "that paper about X", or any question that requires identifying which items in Zotero are relevant — even if the user doesn't say "Zotero" explicitly. Also triggers when the user asks a content question like "which paper explains how to do X" — that requires searching inside documents, not just metadata.
Discover items in the user's Zotero library via the Zotero Web API. Runtime-agnostic — works in Claude Code, Codex, Cursor, or any agent with a shell tool. For reading a specific item, hand off to zotero-read. For comparing, hand off to zotero-compare.
Credentials — the script needs ZOTERO_API_KEY and ZOTERO_USER_ID. It checks, in order:
~/.config/zotero-assistant/env — a local file with KEY=value lines (comments with # and export prefix tolerated). Create it with chmod 600. This is the recommended approach for local use.Get both values at https://www.zotero.org/settings/keys (create a key with library read access; the numeric user ID is on the same page).
— the script uses PEP 723 inline dependencies, bootstrapped by . Install if missing: . First run downloads (~5s); subsequent runs use the cached venv (~20ms).
uvuv runcurl -LsSf https://astral.sh/uv/install.sh | shhttpxThe search script lives at scripts/zotero_find.py in the same directory as this SKILL.md. When installed via npx skills add, this resolves to ~/.agents/skills/zotero-find/scripts/zotero_find.py (with a symlink into ~/.claude/skills/ for Claude Code).
In all the examples below, zotero_find.py stands for that absolute path. A reliable way to find it in any runtime:
SCRIPT="$(dirname "$(realpath "${BASH_SOURCE[0]:-$0}")")/scripts/zotero_find.py"
# or, if you know the canonical install location:
SCRIPT="$HOME/.agents/skills/zotero-find/scripts/zotero_find.py"
| Query type | Example | Strategy |
|---|---|---|
| Author | "anything by Stomakhin" | --mode metadata |
| Topic / keyword | "papers about p-multigrid" | --mode both (default) |
| Specific item | "the Hesthaven book" | --mode metadata |
| Content question | "which paper explains multigrid smoothers" | --mode fulltext |
| Author + year | "Stomakhin 2013" | --mode metadata, then filter results by year field client-side |
| Recent additions | "what did I save recently" | --recent 10 (no --query needed) |
| By tag | "papers tagged important" | add --tag "tagname" to any search |
| Date-filtered | "papers from 2020 onward" | add --since 2020 to any search |
Topic / keyword (default, runs metadata + full-text in parallel and merges):
uv run zotero_find.py --query "multigrid" --limit 15
Author query:
uv run zotero_find.py --query "Stomakhin" --mode metadata --limit 5
Content question (full-text only):
uv run zotero_find.py --query "vertex patch smoother" --mode fulltext --limit 10
Scoped to a collection:
uv run zotero_find.py --query "multigrid" --collection HN3XX96E --limit 10
List all collections:
uv run zotero_find.py --list-collections
Recent additions (no query needed):
uv run zotero_find.py --recent 10
Filter by tag:
uv run zotero_find.py --query "flow" --tag "CFD"
Filter by year (2020 onward):
uv run zotero_find.py --query "multigrid" --since 2020
Combine filters:
uv run zotero_find.py --query "multigrid" --tag "FEM" --since 2022 --limit 10
The script prints JSON to stdout with this shape:
{
"query": "multigrid",
"mode": "both",
"collection": null,
"count": 7,
"results": [
{
"key": "DFXGQV8D",
"title": "Multigrid p-Robustness at Jacobi Speeds...",
"creators": ["Wichrowski"],
"year": "2025",
"itemType": "preprint",
"collections": ["HN3XX96E"],
"abstract": "Vertex-patch smoothers are essential...",
"match": "metadata+fulltext"
},
...
]
}
The match field is the ranking signal:
metadata+fulltext — found in both searches; highest confidence, top of listmetadata — title/creator match; precisefulltext — mentions the term inside the PDF; present in --mode fulltext or --mode bothResults are pre-sorted by confidence. Trust the order.
Only scope to a collection if the query mentions a domain keyword that might match a collection name. Otherwise skip this step.
--list-collections--collection <KEY>Skip scoping entirely when: the query is very specific (exact title, author, citekey) or the user says "across my whole library".
Scannable list, one item per line block. Include the 8-character Zotero key, author(s), year, title, and the match mode:
Found 3 items:
Wichrowski (2025) — Multigrid p-Robustness at Jacobi Speeds: Efficient Matrix-Free Implementation...
DFXGQV8D· match: metadata + fulltext · collection: Multigrid MethodsGhia et al. (1982) — High-Re solutions for incompressible flow using the Navier-Stokes equations and a multigrid method
FA87NP3W· match: metadata + fulltextZhang et al. (2010) — Chebyshev pseudospectral multigrid...
DEF456· match: fulltext only (mentions multigrid in a cited section)
Always include the 8-character Zotero key. zotero-read and zotero-compare need it. Do not extract PDF content in this skill — that's zotero-read's job.
--query "Hu MLS-MPM" returns nothing, break it up: try --query "Hu" and --query "MLS-MPM" separately.--mode fulltext — the term may only exist inside PDF text.--query "the" --limit 3). If that fails, verify ZOTERO_API_KEY and ZOTERO_USER_ID.0 — success1 — HTTP error (network, auth, not-found)2 — missing env vars or invalid arguments