---
name: setup-zotero-references
description: Use when the user wants to configure Zotero references for the current project, when they say "set up zotero", "configure zotero references", "/zotero-context:setup-zotero-references", or when starting a new project that should reference a Zotero library
allowed-tools: |
  Bash(command -v uv)
  Bash(uv --version)
  Bash(curl -LsSf https://astral.sh/uv/install.sh)
  Bash(curl -LsSf https://astral.sh/uv/install.sh | sh)
  Bash(curl -s http://localhost:23119/*)
  Bash(curl -s -o /dev/null -w * http://localhost:23119/*)
  Bash(*list_libraries.py)
  Bash(*list_libraries.py *)
  Bash(*sample_library.py *)
  Bash(*search_items.py *)
  Bash(*get_item.py *)
  Bash(mkdir -p .zotero-context/*)
  Bash(mkdir -p .claude/*)
  Bash(cp *scripts/*.py .zotero-context/bin/*)
  Bash(cp *scripts/*.py .zotero-context/bin/)
  Bash(chmod +x .zotero-context/bin/*.py)
  Bash(chmod +x *scripts/*.py)
  Read
  Write
  Edit
  Grep
  Glob
disable-model-invocation: true
---

# Setup Zotero References

## Overview

One-time bootstrap: ensures `uv` is available, lists the user's Zotero libraries, prompts which to scope to this project, samples each chosen library, derives trigger keywords + a one-line description per library, vendors the search scripts into the repo, and renders the deployed `using-zotero-references` skill with the project-specific keywords.

After this skill runs, future Claude sessions in this repo will auto-trigger `using-zotero-references` for project-relevant topics and call `.zotero-context/bin/search_items.py` for reference lookups. Each vendored script is a [PEP 723](https://peps.python.org/pep-0723/) self-contained inline-metadata script — `uv` reads its dependency declaration and provisions an isolated environment on first invocation. **The user does NOT need to install `pyzotero` globally.**

## When to Use

- User says "set up zotero", "configure zotero references", "/zotero-context:setup-zotero-references"
- User asks how to make a Zotero library available to Claude in this project
- User wants to scope new libraries to an existing `.zotero-context/` config

## Workflow

Run these steps **sequentially**. Stop after each user prompt and wait for input.

### Step 0: Check for `uv`

The vendored scripts use [`uv`](https://github.com/astral-sh/uv) to manage their Python dependencies (no global `pip install` required). Detect it:

```bash
command -v uv
```

If `uv` is found, proceed to Step 1.

If `uv` is not found, ask the user:

> The vendored Zotero scripts use `uv` (from Astral) to manage Python dependencies, so we don't have to install `pyzotero` globally. The official one-line installer is:
>
> ```bash
> curl -LsSf https://astral.sh/uv/install.sh | sh
> ```
>
> This is the install method recommended by the `uv` docs and matches the pattern used by `rustup`, `nvm`, `bun`, and `homebrew`. Install it now? [y/N]

If the user confirms, run:

```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```

Then verify:

```bash
uv --version
```

If `uv` is still not on `$PATH`, point the user at the installer's output (it usually prints a `source $HOME/.local/bin/env` line) and ask them to restart their shell or source that file before re-running this skill.

If the user declines, stop here — the plugin requires `uv` to run the vendored scripts.

### Step 1: Connectivity check

```bash
curl -s -o /dev/null -w "%{http_code}" http://localhost:23119/api/users/0/groups
```

- If `200`: local API works. Proceed.
- Else: check `.env` for `ZOTERO_API_KEY`. If absent, print:
  > Zotero is not reachable. Either start Zotero desktop (preferences → advanced → "Allow other applications") or add `ZOTERO_API_KEY=...` to `.env`. Re-run when ready.
  Then stop.

### Step 2: List libraries

```bash
${CLAUDE_SKILL_DIR}/../../scripts/list_libraries.py
```

(`${CLAUDE_SKILL_DIR}` is the absolute path of this skill's directory, provided by Claude Code. The plugin root is two levels up.) The script's `#!/usr/bin/env -S uv run --script` shebang causes `uv` to provision a one-shot venv on first invocation (cached for subsequent runs).

Display the JSON to the user as a numbered list:

```
1. Personal — 73 items (user/12345)
2. Benji's Cell Ag Library — 187 items (group/5178481)
3. Methods Reading — 42 items (group/9999)
```

### Step 3: Library selection

Ask the user which libraries to scope to this project (multi-select by number, comma-separated). Save the chosen IDs and types.

> **Note:** If the user picks the personal library and `ZOTERO_API_KEY` is not yet set, the stored `library_id` will be `0` (a local-only convention). This breaks web-API fallback when Zotero desktop is closed. Remind the user that adding `ZOTERO_API_KEY` to `.env` before or after setup lets `list_libraries.py` resolve a portable numeric user ID that works with both APIs.

### Step 4: Sample fetch + keyword analysis

For each chosen library:

```bash
${CLAUDE_SKILL_DIR}/../../scripts/sample_library.py --lib <ID> --type {user|group} --limit 30
```

(`sample_library.py` bypasses config loading, so it is safe to call before Step 5 writes `config.json`.)

Read the sample output. **You (the running Claude session) propose:**
- 8–15 domain keywords representing the library's topical focus (no generic terms like "research", "paper").
- A 1–2 sentence description of the library suitable for CLAUDE.md.

Show your proposal to the user:

```
For library "Benji's Cell Ag Library":

Description: Curated cellular agriculture literature spanning precision fermentation, scaffold biomaterials, and cultivated meat process development.

Trigger keywords:
- cellular agriculture
- precision fermentation
- ...
```

Wait for user edits/approval before proceeding.

### Step 5: Write `.zotero-context/config.json`

Combine all libraries' descriptions and union all keywords into one project-level list (deduped).

```json
{
  "version": 1,
  "libraries": [
    {"id": 5178481, "type": "group", "name": "Benji's Cell Ag Library", "description": "..."},
    ...
  ],
  "trigger_keywords": ["cellular agriculture", "precision fermentation", ...],
  "defaults": {
    "include_notes": true,
    "include_annotations": true,
    "output": "full"
  }
}
```

### Step 6: Vendor scripts

```bash
mkdir -p .zotero-context/bin
cp ${CLAUDE_SKILL_DIR}/../../scripts/{common,zotero_client,list_libraries,search_items,get_item,enrich_abstract,sample_library}.py .zotero-context/bin/
chmod +x .zotero-context/bin/{list_libraries,search_items,get_item,enrich_abstract,sample_library}.py
```

The five entry-point scripts are made executable so the deployed skill can invoke them directly via shebang. `common.py` and `zotero_client.py` are imported as modules by the others and don't need to be executable.

### Step 7: Render the deployed skill

Read `${CLAUDE_SKILL_DIR}/../../templates/using-zotero-references/SKILL.md.template`. Substitute:

- `{{KEYWORDS}}` → comma-joined `trigger_keywords` from config.
- `{{LIBRARY_DESCRIPTIONS}}` → bullet list, one per library: `- {name}: {description}`.

Write the result to `.claude/skills/using-zotero-references/SKILL.md`.

### Step 8: Gitignore

- Always: ensure `.env` is in `.gitignore`. If absent, add it.
- Prompt: "Should `.zotero-context/` be gitignored? Default is **no** — committing it makes the repo self-contained for collaborators and future worktrees. (y/N)"
- If yes, add `.zotero-context/` to `.gitignore`.

### Step 9: CLAUDE.md update

Append (or update an existing managed block):

```markdown
<!-- BEGIN zotero-context -->
## Zotero References

This project uses the `using-zotero-references` skill to look up curated literature in:

- {library 1 name}: {description}
- {library 2 name}: {description}

The skill auto-triggers on project-relevant topics and calls `.zotero-context/bin/search_items.py` (a self-contained `uv run --script` shebang). Run `/zotero-context:enrich-zotero-abstracts` to fix items with missing abstracts.
<!-- END zotero-context -->
```

If the block already exists, replace it; otherwise append at the end of CLAUDE.md (or AGENTS.md if CLAUDE.md doesn't exist).

### Step 10: Smoke test

```bash
.zotero-context/bin/search_items.py --query "<first trigger keyword>" --limit 2
```

The first invocation triggers `uv` to install `pyzotero` into a script-scoped cache (~5MB, one-time). Show the user the JSON output and confirm setup is working.

## Idempotency

When `.zotero-context/config.json` already exists, offer:

- **Add libraries** — re-list, multi-select additions, re-derive keywords for new ones, merge into config.
- **Refresh keywords** — re-sample chosen libraries, re-propose, replace `trigger_keywords` and re-render the deployed skill.
- **Re-vendor scripts** — copy latest plugin scripts to `.zotero-context/bin/` (use this after the plugin updates).

Never silently overwrite — confirm action with user.

## Common Mistakes

- Skipping Step 0 (uv check) — vendored scripts will fail with `env: 'uv': No such file or directory` if `uv` isn't on `$PATH`.
- Skipping Step 4 and using library names as keywords. Library names are vague; sample-derived keywords trigger more reliably.
- Hand-editing `.claude/skills/using-zotero-references/SKILL.md` and losing the changes on next setup re-run. Edit the template instead, then re-render.
- Forgetting Step 6 (vendoring) — the deployed skill expects scripts at `.zotero-context/bin/`.
- Forgetting Step 6's `chmod +x` — without the executable bit the shebang doesn't fire and Claude gets a "Permission denied" error.
