Skip to content

Changelog

All notable changes to this project will be documented in this file.

Format follows Keep a Changelog, versioning follows Semantic Versioning.

[Unreleased]

[0.13.0] - 2026-08-17

Changed

  • The workspace concept is gone — it lasted one release as a synonym for "collection", and two names for one thing was one name too many. Scoped ranked retrieval now lives in zot search --ranked and zot ask --collection.

Added

  • zot search --ranked — relevance-ranked results (scores + snippets) from the index-free engine introduced in 0.12.0, with optional --collection COLL scope (name or key).
  • zot collection remove ITEM COLL — remove an item from a collection without deleting it from the library, completing the soft-link membership loop with collection move. MCP: new collection_remove tool; the search tool gained a ranked parameter (39 tools total).

Removed

  • zot workspace command group and the --workspace flag on zot ask (renamed --collection). MCP: the workspace_query tool (use search with ranked=True); the ask tool's workspace parameter was renamed collection.

[0.12.0] - 2026-08-17

Changed

  • A workspace is now just a Zotero collectionzot workspace query and zot ask resolve --workspace to a collection (name or key; omit it to search the whole library). Manage membership in the Zotero app or via zot collection *; zot stores nothing locally.
  • Retrieval is now index-free — a two-stage ranker replaces the pre-built RAG index: stage 1 scores items with idf-weighted term coverage from Zotero's own full-text index tables, fused via reciprocal rank fusion with metadata matching (title/abstract/creators/tags/notes); stage 2 (ask) extracts PDF passages on the fly around query terms (pdfium, cached). Results are always fresh — there is no index to build or rebuild.
  • zot ask's --workspace flag is now optional.

Removed

  • zot workspace curation and indexing subcommands (new, delete, add, remove, list, show, export, import, search, index) — superseded by Zotero collections. Local workspaces under ~/.config/zot/workspaces/ (JSON and *.idx.sqlite) are no longer read and can be deleted; recreate the same sets as collections in Zotero.
  • Embedding support — the [embedding] config section, the ZOT_EMBEDDING_URL / ZOT_EMBEDDING_KEY / ZOT_EMBEDDING_MODEL / ZOT_EMBEDDING_PROVIDER environment variables, and the aliyun/jina providers. The unused openai dependency was dropped from the package.
  • MCP tools: workspace_new, workspace_delete, workspace_add, workspace_remove, workspace_list, workspace_show, workspace_export, workspace_import, workspace_search, workspace_index (39 tools remain). workspace_query was reworked (name → optional workspace, returns ranked items with snippets) and a new ask tool mirrors the CLI.

[0.11.0] - 2026-08-17

Changed

  • PyPI distribution renamed zotero-cli-cczotero-cli-ai. Install or upgrade with uv tool install zotero-cli-ai (or pipx/pip). The zot command, the zotero_cli_cc Python module, the Zotero bridge plugin ID, and all config/cache paths are unchanged. zotero-cli-cc on PyPI receives no further updates; zotero-cli is an unrelated older project.
  • Project branding unified as "zotero-cli — A Zotero CLI for Any AI Agent" (was positioned as Claude Code-only).

Fixed

  • zot config profile set was a silent no-op on configs written by zot itself (single-quoted TOML values weren't matched); save_config now emits properly escaped double-quoted TOML, and config.toml is written 0600 with the config dir at 0700 (#89).
  • update-status and pdf broke the JSON agent contract — prose mixed into stdout. All machine output now goes through the JSON envelope, prose to stderr (#89).
  • Library-scale reads could exceed SQLITE_MAX_VARIABLE_NUMBER — the remaining IN (...) clauses are batched, with a Python-side sort fallback for very large sorted queries; database paths containing ?, # or % no longer corrupt the SQLite URI (#89).
  • zot open on Windows uses os.startfile instead of shell=True; MinerU split chunks go to a temporary directory instead of polluting Zotero storage (#89).

[0.10.0] - 2026-07-15

Added

  • OpenAI-compatible embedding endpoints — new provider = "openai" (ZOT_EMBEDDING_PROVIDER=openai) routes any OpenAI-compatible /v1/embeddings endpoint (Aliyun Bailian workspace URLs, LiteLLM, Ollama, vLLM, ...) with configurable base URL, API key, and model. The aliyun provider now also honors ZOT_EMBEDDING_URL / [embedding] url as a custom base URL — needed since Bailian moved to per-workspace endpoints — and falls back to the dashscope default when unset. Both base URLs and full .../embeddings URLs are accepted (#84, #86).

Fixed

  • zot workspace query crashed on large indices with sqlite3.OperationalError: too many SQL variables — BM25 term lookup bound every chunk id into a single IN (...) clause, exceeding SQLite's SQLITE_MAX_VARIABLE_NUMBER. Now queried in batches of 900 ids (#83, #85).

[0.9.0] - 2026-07-14

Added

  • zot attachment path KEY --all (-a) — list every PDF attachment of an item, one local path per line, instead of just the first. Built for items that now routinely ship an appendix or supplementary PDF alongside the main article. In JSON mode it returns {item_key, count, attachments: [...]} with each entry carrying attachment_key, path, filename, exists, and mime_type. Attachments with no local file are skipped; not_found is returned only when the item has no PDF at all, or no PDF has been synced to local storage. Default (no flag) behaviour is unchanged — still prints the first PDF's path.
  • New reader API ZoteroReader.get_pdf_attachments(key) returning all PDFs; get_pdf_attachment (first PDF) is now a thin wrapper over it.
  • Tokenized multi-word searchzot search splits multi-word queries into independent word matches: each word may hit title, creator, tag, or full text separately, and results are the intersection across words (#76).
  • Group-library support for zot attach --via-bridge — the bridge maps the Web-API group id to the desktop's internal libraryID; requires bridge plugin v0.4.0 (the CLI checks the plugin version and fails with bridge_missing rather than importing into the personal library) (#72).
  • zot attach auto-detects the bridge — imports through the running Zotero desktop when reachable, otherwise falls back to the Web API; the cloud path now reports an honest data.result (created vs exists) (#71).
  • --idempotency-key on zot enrich and zot rename so agent retries are safe on those commands too (#79).

Changed

  • Schema version bumped to 1.9.0 (additive: new --all option on attachment path).

Fixed

  • Migrated remaining print_error call sites to emit_error so failures exit non-zero with typed codes; cite, export, stats, summarize, and tag now emit proper JSON envelopes (#78).
  • MCP server no longer re-creates the writer connection per call (caching); workspace-index errors are now logged instead of swallowed; mineru fallback reports the underlying error; save_config preserves other profiles; removed an N+1 query in get_collection_items; workspace search is tokenized; duplicate detection scores all pairs (#77).

[0.8.0] - 2026-06-03

Added

  • zot orphans — find and clean attachments whose stored file is missing from local storage/ (Zotero's "the attached file could not be found"). orphans list classifies each as dead (no copy anywhere — safe to remove), recoverable (server still has it — fix by file-sync), or unknown; orphans clean deletes the dead ones via the Web API (--dry-run / --yes / --idempotency-key; --include-recoverable to also drop server-held ones). Mirrored as the read-only find_orphans MCP tool.
  • zot attach --via-bridge — import a file through the running Zotero desktop (the zot-cli-bridge plugin's new POST /zot-cli/import-file endpoint → Zotero.Attachments.importFromFile) so the binary lands in local storage immediately instead of cloud-only, cooperating with attachment movers like zotero-attanger. Bridge plugin bumped to v0.3.0; also exposed as attach(via_bridge=True) over MCP.
  • GROBID extractor (pdf.extractor = "grobid" / ZOT_GROBID_URL): a references/structure tier backed by a running GROBID service (default http://localhost:8070). Adds extract_references() to the extractor interface and a zot pdf KEY --references flag that returns the parsed reference list (title / authors / year / journal / DOI). Lighter than the vision-model extractors; intended for citation verification and metadata completion. GROBID is not bundled — the user runs the service.
  • references MCP tool in zot mcp serve: exposes the GROBID-parsed reference list (title / authors / year / journal / DOI) over MCP, so an agent can verify citations without shelling out. Returns error + hint when no GROBID service is reachable.
  • pdfplumber table extractor (zotero-cli-cc[pdfplumber]): pure-Python table extraction (no ML / GPU / network). Adds extract_tables() to the extractor interface, a zot pdf KEY --tables flag, and a tables MCP tool. All-empty tables (spurious grids pdfplumber detects on figure-heavy pages) are filtered out so only tables with real cell content are returned.
  • pymupdf4llm bundled into the [pymupdf] extra: installing the extra now also enables higher-quality local Markdown output (no API / network — the PyMuPdfExtractor already uses it when present).

Changed

  • License: relicensed from CC-BY-NC-4.0 to a dual license — AGPL-3.0-or-later for open-source use plus a separate commercial license (see LICENSE / LICENSE-COMMERCIAL). The base install ships no AGPL runtime code (default pdfium extractor; pymupdf is opt-in).
  • zot attach now reports where the file landed via data.stored ("cloud" for the default Web-API upload, "local" for --via-bridge), and warns that a cloud upload only reaches local storage/ after a desktop file-sync. schema_version 1.6.0 → 1.7.0.

[0.7.0] - 2026-05-29

Added

  • zot ask "QUESTION" --workspace NAME retrieves a citation-keyed evidence pack from a workspace's RAG index (hybrid BM25 + embedding retrieval via reciprocal rank fusion) and returns it with answer_instructions, so the calling agent can synthesize a grounded, cited answer. Unlike workspace query (which dumps ranked chunks), each evidence entry is tagged with its Zotero item key as the cite_key and carries per-method scores. Following the same contract as summarize, zot prepares the context but calls no generative LLM itself — the agent is the model. Options: --evidence-k (default 12) and --mode auto|bm25|semantic|hybrid.
  • Schema version bumped to 1.6.0.

Changed

  • Default PDF extractor is now pdfium (pypdfium2, BSD/Apache-licensed) instead of pymupdf. PyMuPDF is AGPL/Artifex-licensed, so it is no longer a core dependency — a plain pip install zotero-cli-cc now ships no AGPL code, making it usable in commercial/closed-source products without an Artifex license. PyMuPDF moves to an optional extra:
pip install 'zotero-cli-cc[pymupdf]'

Install the extra to enable the pymupdf extractor, which adds PDF annotation/highlight extraction and higher-quality markdown. The default pdfium extractor covers text and DOI extraction; it returns an empty list for annotations. The MinerU fallback now falls back to pdfium. Select an extractor explicitly with extractor = "..." in config or ZOT_PDF_EXTRACTOR.

[0.6.0] - 2026-05-28

Added

  • zot enrich KEY... writes journal metrics (impact factor, JCR/CAS quartile, core-journal flags, …) into an item's Extra field. Deliberately source-neutral: values come from inline --set "Label=value" flags or a user-maintained --from-map TOML table (matched by journal name) — zot ships no journal data and calls no third-party API, so it stays independent of any external product. Metrics are written in a <!-- zot:metrics --> block, so re-running replaces only that block (idempotent) and preserves other Extra content. Supports --dry-run.
  • zot rename KEY... renames an item's PDF attachment files from its metadata via the bridge plugin. The default template is {journal}_{year}_{title} (tokens {journal} {year} {title} {fulltitle} {shorttitle} {author}; {title} prefers the Short Title field when set). {journal} is resolved from a Jab/# tag or an item-type-aware abbreviation (arXiv → Pre, single-word venues kept whole so Nature stays Nature). Empty tokens are collapsed (no Pre__x) and names are truncated to a filesystem-safe length. Non-PDF attachments (Excel/Word/snapshots) are filtered out by content type; supplementary PDFs are detected by filename and get an _SI suffix so names never collide. Supports --dry-run, --main-only, --force, --template, and --attachment/--name for explicit single-file renames. Requires the zot-cli-bridge plugin v0.2.0+ (re-run zot bridge install). meta.schema_version is bumped 1.4.0 → 1.5.0.

[0.5.0] - 2026-05-28

Added

  • zot find-pdf KEY triggers Zotero desktop's "Find Full Text" over a local bridge plugin, so the CLI can fetch and attach PDFs that the Zotero Web API cannot reach (paywalled content behind the desktop's configured resolvers, authenticated sessions, and institutional proxies). Ships with zot bridge install / status / uninstall to package the bundled zot-cli-bridge plugin into an .xpi and guide installation. Both commands are also exposed over MCP. meta.schema_version is bumped 1.3.0 → 1.4.0 (#43).
  • zot workspace index --skip-tag excludes attachments carrying a given tag from the RAG index (default skip-index), so large or irrelevant PDFs can be kept out of the index. Also available on the MCP workspace_index tool (#44, #46).

Fixed

  • On Windows with a CJK (GBK/CP936) system locale, zot crashed with UnicodeEncodeError whenever output contained characters outside the GBK range (e.g. emoji). stdout/stderr are now reconfigured to UTF-8 at startup on Windows when the encoding is not already UTF-8 (#48).

Changed

  • The bundled Claude Code skill was split from a single SKILL.md into a concise entry point plus on-demand references/ files (commands, workspaces, workflows, windows-encoding), and now documents the find-pdf / bridge commands and workspace index --skip-tag (#49).

[0.4.4] - 2026-05-14

Fixed

  • zot add --doi created empty items because the Zotero Web API does not auto-resolve DOIs the way the desktop translator does. The CLI now fetches metadata from Crossref (title, creators, journal, volume/issue/ pages, date, ISSN, abstract, publisher, language) and merges it into the item template before posting, so created items are populated, not bare shells. Same fix applies to the MCP add / add_from_pdf handlers. Pass --no-resolve to opt out, set ZOT_CROSSREF_MAILTO to join Crossref's polite pool. meta.schema_version is bumped 1.1.0 → 1.2.0 for the additive envelope slot (data.resolved / data.resolve_warning) (#41, #42).

[0.4.3] - 2026-05-11

Fixed

  • Update-available banner hard-coded uv tool upgrade zotero-cli-cc, which is wrong for users who installed via pip / conda / pipx. The suggested command is now detected from sys.executable (uv tool / pipx) with a pip install -U fallback that works for pip, conda, and system installs (#31).

[0.4.2] - 2026-05-10

Fixed

  • Update-available nag fired indefinitely after upgrading because __version__ was hardcoded in __init__.py and missed in the 0.4.0 / 0.4.1 bumps, so installed copies of 0.4.1 reported themselves as 0.3.0. Version is now sourced from package metadata (importlib.metadata), making pyproject.toml the single source of truth (#30).

[0.4.1] - 2026-05-05

Embedding configuration cleanup. The provider-specific Aliyun key (aliyun_api_key / ZOT_EMBEDDING_ALIYUN_KEY) and the implicit provider="auto" mode were leaking the multi-provider routing implementation into the user-facing config without a symmetrical counterpart for Jina. Single-provider, single-key surface is cleaner.

Changed

  • [embedding] provider default is now "jina" (was "auto"). Set provider = "aliyun" (or ZOT_EMBEDDING_PROVIDER=aliyun) to use Aliyun DashScope.

Removed (Breaking, very rare)

  • [embedding] aliyun_api_key config key — use the unified [embedding] api_key instead.
  • ZOT_EMBEDDING_ALIYUN_KEY env var — use ZOT_EMBEDDING_KEY instead.
  • provider = "auto" mode — pick a provider explicitly.

If you upgraded to 0.4.0 within the past hour and were already using the Aliyun-specific key, rename it to api_key / ZOT_EMBEDDING_KEY and set provider = "aliyun".

[0.4.0] - 2026-05-05

PDF extraction overhaul, envelope routing for the rest of the --json surface, typed exit codes wired up across all command error paths, and a CI repair pass. schema_version bumps to 1.1.0.

Added

  • MinerU PDF extractor alongside the existing pymupdf-based extractor, with a new BasePdfExtractor abstract class and automatic fallback when MinerU fails (zot pdf KEY --extractor mineru). Configure via [pdf] extractor, [pdf] mineru_token, or MINERU_TOKEN / ZOT_PDF_EXTRACTOR env vars.
  • zot pdf --outline — list every heading in the document as a numbered outline so agents can navigate without dumping the full text.
  • zot pdf --section N — extract just the content under the N-th heading from --outline. Useful for "show me the methods section" workflows.
  • zot workspace index --extractor — choose the PDF extractor used during RAG indexing.
  • Embedding provider router with first-class support for Aliyun (DashScope, OpenAI-compatible) and Jina endpoints. Routes via the new [embedding] provider config key / ZOT_EMBEDDING_PROVIDER env var. (0.4.1 simplified the surface — see below.)
  • Attachment resolver that handles storage: paths, file:// URLs, Zotero's attachments: paths, Windows drive letters, and base-attachment prefs. PDFs in non-default storage directories now resolve correctly.
  • progress_callback plumbing through the PDF extraction path so MinerU batch operations and per-PDF extraction surface progress to the caller.

Changed

  • Envelope routing extended to the remaining --json commands: zot pdf (incl. --outline / --section), zot workspace list, zot workspace query, and zot config cache list now emit the standard {ok, data, meta} envelope. workspace query data becomes {mode, results} rather than the bare results list.
  • schema_version 1.0.0 → 1.1.0 to reflect the envelope-coverage extension and the typed-exit-code parity. docs/agent-interface.md updated.
  • Typed exit codes wired across all command error paths. Previously many error paths called print_error(...); return, printing the error message but silently exiting 0. They now use emit_error(...) with the appropriate typed code:
  • not_found (4): item / PDF / workspace / collection / profile / index / section missing — affects cite, export, summarize, open, pdf, workspace delete/add/remove/show/export/import/search/index/query, config profile_set.
  • validation_error (3): bad page range in pdf, missing required source flag in workspace import, invalid workspace name.
  • auth_missing (2): all tag / trash restore / collection write commands when API credentials aren't configured.
  • conflict (6): workspace new when the workspace already exists, and zot duplicates now exits 6 when duplicates are found so agents can branch on if zot duplicates …; then …; else act_on_dups; fi.
  • runtime_error (1): caught PdfExtractionError in pdf and ZoteroWriteError in collection move/delete/rename.
  • zot relate KEY with no related items is now a normal exit-0 outcome (matching zot search on no matches) rather than an error message.
  • config cache list robustness: graceful fallback when the cache DB is unreachable; closes the connection in a finally block.

Fixed

  • 20 pre-existing test failures on main repaired (some were envelope-shape drift between tests and production; the rest were genuine exit-code regressions covered by the migration above). The ci.yml pytest run goes green again.
  • tests/test_extracts_text no longer breaks on hosts without ~/.config/zot/config.toml. The previous over-broad Path.exists mock also patched load_pdf_config's file-existence check; tightened to a targeted load_pdf_config mock.

Breaking

  • Tools / agents parsing --json output from zot pdf, zot workspace list, zot workspace query, or zot config cache list need to unwrap the standard envelope (result["data"]). Other commands were already enveloped; this brings the rest of the surface into line.
  • Error paths that previously exited 0 with a printed message now exit with their typed code (1, 2, 3, 4, or 6). Scripts that ran zot cite NONEXIST && echo ok and similar will now correctly fail.
  • zot duplicates exits 6 (CONFLICT) when duplicates are detected. Scripts that ignored the exit code or used if zot duplicates; then will need to invert the branch.

[0.3.0] - 2026-04-15

Agent-native CLI interface. zot now serves humans, AI agents (Claude Code, Codex), and orchestrators from a single surface. See docs/agent-interface.md for the full contract.

Added

  • Stable JSON envelope for every command: {"ok": true, "data": ..., "meta": {...}} on success, {"ok": false, "error": {"code", "message", "retryable"}, "meta": {...}} on failure, {"ok": "partial", "data": {"succeeded", "failed"}} for batch operations.
  • TTY auto-detection: --json is now implicit when stdout is not a TTY. Agents piping zot output always get parseable JSON without remembering a flag. Override with ZOT_FORMAT=json|table|text.
  • Typed exit codes: 0 success, 1 runtime error, 2 auth error, 3 validation error, 4 not-found, 5 network error, 6 conflict. Orchestrators can route failures deterministically.
  • zot schema [command...] — machine-readable introspection for the full CLI tree. Each entry carries name, params (typed), safety_tier, since, deprecated, and nested subcommands. Agents can discover every command without a README.
  • Safety tiers in --help: top-level help groups commands into Read / Write (MUTATES LIBRARY) / Destructive sections. Destructive command help carries a "MUTATES LIBRARY" warning.
  • --dry-run on all mutating commands: add, update, note --add, attach, delete, trash restore. Preview shape: {"ok": true, "dry_run": true, "data": {"would": ...}}.
  • --idempotency-key on add, update, note --add, attach, delete. SQLite-backed cache at $ZOT_CACHE_DIR/idempotency.db (default ~/.cache/zotero-cli-cc) with 24h TTL. Retried calls carrying the same key return the original envelope and never duplicate the upstream mutation.
  • meta slot on every envelope: request_id (uuid), latency_ms, schema_version, cli_version. Mutating commands also set sync_required: true.
  • next hints in success envelopes: add, update, delete, note --add, attach suggest plausible follow-up commands so the agent saves a planning turn.
  • retryable field on every error: network / 5xx / rate-limit → retryable: true; not-found / validation / 4xx → retryable: false. ZoteroWriteError carries code, retryable, retry_after_seconds.
  • --stream mode on search, list, recent — emits NDJSON (one item per line) plus a summary line. Agents can process long result sets incrementally.
  • Structured stderr progress events for long-running commands (add --from-file, summarize-all): NDJSON {event, phase, done, total, elapsed_ms, request_id} so agents can detect liveness without blocking on the final stdout envelope.
  • Confirmation-required guard on destructive commands: zot delete K1 with non-interactive stdin and no --yes/--dry-run returns a structured confirmation_required error instead of blocking.
  • New exit_codes.py, core/idempotency.py modules.
  • 43 new tests across test_agent_interface.py, test_agent_p1.py, test_agent_p2.py.

Changed

  • format_error / format_items / format_item_detail / format_collections / format_notes / format_duplicates now wrap JSON output in the envelope. Callers that parsed raw arrays must unwrap via env["data"].
  • Human error messages moved from stdout to stderr via the new print_error helper.
  • ErrorInfo dataclass gains code and retryable fields.
  • Top-level CLI group uses a custom TieredGroup help renderer.

Breaking

  • JSON output contract: callers parsing bare arrays or dicts must now read from env["data"]. Error responses now nest under env["error"] with code / message / retryable fields instead of a flat {"error": "..."}.
  • Exit codes: previously 1 for all failures; now distinct codes per failure class. Scripts checking for any non-zero exit remain valid.

[0.1.6] - 2026-03-24

Added

  • zot duplicates [--by doi|title|both] [--threshold 0.85] — find duplicate items by DOI match or fuzzy title similarity
  • zot trash list — view trashed items
  • zot trash restore KEY [KEY ...] — restore item(s) from trash via Zotero API
  • zot attach KEY --file paper.pdf — upload file attachments to existing items
  • zot add --pdf paper.pdf — extract DOI from PDF, create item, and attach file
  • --library group:<id> — global option for group library support across all commands
  • DuplicateGroup model for structured duplicate detection results
  • resolve_library_id() helper for group library resolution
  • All 5 new features available as MCP tools (duplicates, trash_list, trash_restore, attach, add_from_pdf)
  • library parameter added to all existing MCP tools for group library access
  • 43 new tests (314 total)

Changed

  • ZoteroReader accepts library_id parameter for multi-library filtering
  • ZoteroWriter accepts library_type parameter for group library writes
  • MCP server uses per-library reader cache instead of global singleton

[0.1.5] - 2026-03-24

Added

  • zot search --type journalArticle — filter search/list results by item type
  • zot search --sort dateAdded --direction desc — sort results by date, title, or creator
  • zot recent --days 7 — show recently added or modified items
  • zot update KEY --title/--date/--field — update item metadata via Zotero API
  • zot pdf KEY --annotations — extract PDF annotations (highlights, notes, comments)
  • --detail full now shows journal, volume, issue, pages, ISSN, publisher, citation key
  • summarize now shows URL, tags, source info, abstract, and notes
  • All 5 new features available as MCP tools (search, list_items, recent, update, annotations)
  • 37 new tests (271 total)

Fixed

  • --detail full output was identical to standard detail level
  • summarize command only showed basic metadata without abstract or source info

[0.1.3] - 2026-03-23

Added

  • zot cite command — format citations in APA, Nature, or Vancouver style and copy to clipboard
  • zot add --from-file — batch import DOIs/URLs from a text file (one per line, supports # comments)
  • RIS export format (zot export KEY --format ris) with 11 Zotero type mappings
  • Usage examples in --help text for 13 commands
  • PyPI/CI/Python/License badges in README
  • pipx as install option
  • Shell completion install instructions (zsh/bash/fish)

[0.1.2] - 2026-03-22

Added

  • --dry-run flag for delete, collection delete, and tag commands
  • --offset pagination for summarize-all and reader.search()
  • PdfExtractionError with graceful handling of corrupted/password-protected PDFs
  • Page range validation — error when requested pages exceed document length
  • API timeout (30s) on ZoteroWriter to prevent hanging on unresponsive servers
  • _excluded_filter() method returning parameterized SQL placeholders
  • markdownify dependency for proper HTML-to-Markdown conversion
  • 19 new tests covering dry-run, offset, PDF errors, timeouts, and write error handling (199 total)

Changed

  • Exception handling narrowed from except Exception to except ZoteroWriteError in all write commands
  • HTML-to-Markdown conversion replaced from naive regex to markdownify library
  • WAL lock fallback uses TemporaryDirectory instead of manual mkdtemp/rmtree
  • __enter__/__exit__ type annotations fixed, removed type: ignore
  • Search queries use parameterized SQL (? placeholders) instead of string interpolation

Fixed

  • Unguarded writer calls in add, delete, tag, note commands now catch ZoteroWriteError
  • httpx.TimeoutException now caught alongside ConnectError in all writer methods

[0.1.1] - 2026-03-22

Added

  • zot stats command for library statistics
  • zot open command for launching PDFs and URLs
  • CSL-JSON export format
  • Shared MCP reader instance with atexit cleanup
  • note_update MCP tool
  • Collection key filter for search
  • Unified Zotero skill routing between zot and rak

Fixed

  • Excluded type IDs looked up dynamically instead of hardcoding
  • Fulltext search routed to rak for semantic search
  • Version sync, CI workflow, temp file leak, BibTeX escaping, search N+1

[0.1.0] - 2026-03-21

Added

  • Initial release
  • SQLite-based read operations (search, list, read, export, relate, notes, collections, attachments, PDF extraction)
  • Web API write operations via pyzotero (add, delete, tag, note, collection CRUD)
  • MCP server with 17 tools (11 read + 6 write)
  • summarize-all and collection reorganize for AI classification
  • PDF text extraction with SQLite-backed caching
  • Rich table + JSON output formatting
  • TOML-based configuration with profile support
  • WAL lock handling with automatic fallback
  • Batch query optimization (N+1 prevention)
  • BibTeX and CSL-JSON citation export
  • Related items discovery (explicit relations + implicit via shared tags/collections)