zotcli 0.1.2__tar.gz → 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. {zotcli-0.1.2 → zotcli-0.2.1}/.gitignore +6 -0
  2. zotcli-0.2.1/CHANGELOG.md +112 -0
  3. {zotcli-0.1.2 → zotcli-0.2.1}/PKG-INFO +180 -152
  4. zotcli-0.2.1/PLAN_WRITE.md +883 -0
  5. {zotcli-0.1.2 → zotcli-0.2.1}/README.md +167 -150
  6. zotcli-0.2.1/SKILL.md +305 -0
  7. zotcli-0.2.1/docs/architecture-write.md +221 -0
  8. zotcli-0.2.1/docs/commands.md +514 -0
  9. {zotcli-0.1.2 → zotcli-0.2.1}/pyproject.toml +6 -3
  10. {zotcli-0.1.2 → zotcli-0.2.1}/src/zotcli/__init__.py +1 -1
  11. zotcli-0.2.1/src/zotcli/cli/add.py +2267 -0
  12. {zotcli-0.1.2 → zotcli-0.2.1}/src/zotcli/cli/collections.py +41 -0
  13. zotcli-0.2.1/src/zotcli/cli/config_cmd.py +55 -0
  14. {zotcli-0.1.2 → zotcli-0.2.1}/src/zotcli/cli/export.py +30 -20
  15. {zotcli-0.1.2 → zotcli-0.2.1}/src/zotcli/cli/items.py +10 -1
  16. zotcli-0.2.1/src/zotcli/cli/main.py +142 -0
  17. {zotcli-0.1.2 → zotcli-0.2.1}/src/zotcli/cli/render.py +6 -1
  18. zotcli-0.2.1/src/zotcli/config.py +252 -0
  19. zotcli-0.2.1/src/zotcli/logging_setup.py +108 -0
  20. {zotcli-0.1.2 → zotcli-0.2.1}/src/zotcli/models.py +13 -1
  21. zotcli-0.2.1/src/zotcli/paths.py +100 -0
  22. zotcli-0.2.1/src/zotcli/write/__init__.py +5 -0
  23. zotcli-0.2.1/src/zotcli/write/browser.py +321 -0
  24. zotcli-0.2.1/src/zotcli/write/citation_pipeline.py +228 -0
  25. zotcli-0.2.1/src/zotcli/write/collection_assign.py +57 -0
  26. zotcli-0.2.1/src/zotcli/write/connector_client.py +529 -0
  27. zotcli-0.2.1/src/zotcli/write/credentials.py +162 -0
  28. zotcli-0.2.1/src/zotcli/write/csl_json.py +268 -0
  29. zotcli-0.2.1/src/zotcli/write/dedup.py +186 -0
  30. zotcli-0.2.1/src/zotcli/write/identifiers.py +225 -0
  31. zotcli-0.2.1/src/zotcli/write/pdf.py +190 -0
  32. zotcli-0.2.1/src/zotcli/write/preflight.py +81 -0
  33. zotcli-0.2.1/src/zotcli/write/recognize.py +92 -0
  34. zotcli-0.2.1/src/zotcli/write/resolvers/__init__.py +65 -0
  35. zotcli-0.2.1/src/zotcli/write/resolvers/arxiv.py +174 -0
  36. zotcli-0.2.1/src/zotcli/write/resolvers/crossref.py +237 -0
  37. zotcli-0.2.1/src/zotcli/write/resolvers/ieee.py +220 -0
  38. zotcli-0.2.1/src/zotcli/write/resolvers/openalex.py +275 -0
  39. zotcli-0.2.1/src/zotcli/write/resolvers/openlibrary.py +153 -0
  40. zotcli-0.2.1/src/zotcli/write/resolvers/pubmed.py +229 -0
  41. zotcli-0.2.1/src/zotcli/write/resolvers/sciencedirect.py +173 -0
  42. zotcli-0.2.1/src/zotcli/write/resolvers/semantic_scholar.py +135 -0
  43. zotcli-0.2.1/src/zotcli/write/resolvers/unpaywall.py +144 -0
  44. zotcli-0.2.1/src/zotcli/write/session.py +255 -0
  45. zotcli-0.2.1/tests/fixtures/csl/crossref_numpy.json +52 -0
  46. zotcli-0.2.1/tests/fixtures/sample.bib +11 -0
  47. zotcli-0.2.1/tests/fixtures/sample.epub +0 -0
  48. zotcli-0.2.1/tests/fixtures/sample.pdf +25 -0
  49. zotcli-0.2.1/tests/fixtures/sample.ris +14 -0
  50. zotcli-0.2.1/tests/integration/__init__.py +0 -0
  51. zotcli-0.2.1/tests/integration/test_add_cite.py +316 -0
  52. zotcli-0.2.1/tests/integration/test_add_file.py +340 -0
  53. zotcli-0.2.1/tests/integration/test_add_import.py +388 -0
  54. zotcli-0.2.1/tests/integration/test_add_pipeline.py +522 -0
  55. zotcli-0.2.1/tests/integration/test_add_url.py +343 -0
  56. zotcli-0.2.1/tests/integration/test_auto_detect.py +339 -0
  57. zotcli-0.2.1/tests/integration/test_batch.py +335 -0
  58. zotcli-0.2.1/tests/integration/test_connector_client.py +115 -0
  59. zotcli-0.2.1/tests/integration/test_verbose.py +210 -0
  60. zotcli-0.2.1/tests/integration/test_with_pdf.py +382 -0
  61. zotcli-0.2.1/tests/unit/__init__.py +0 -0
  62. zotcli-0.2.1/tests/unit/test_browser_optional_dep.py +139 -0
  63. zotcli-0.2.1/tests/unit/test_citation_pipeline.py +368 -0
  64. zotcli-0.2.1/tests/unit/test_config_write_section.py +148 -0
  65. zotcli-0.2.1/tests/unit/test_credentials.py +178 -0
  66. zotcli-0.2.1/tests/unit/test_csl_json.py +309 -0
  67. zotcli-0.2.1/tests/unit/test_dedup.py +360 -0
  68. zotcli-0.2.1/tests/unit/test_identifiers.py +304 -0
  69. zotcli-0.2.1/tests/unit/test_logging_setup.py +143 -0
  70. zotcli-0.2.1/tests/unit/test_paths.py +199 -0
  71. zotcli-0.2.1/tests/unit/test_pdf.py +262 -0
  72. zotcli-0.2.1/tests/unit/test_recognize.py +216 -0
  73. zotcli-0.2.1/tests/unit/test_resolvers/__init__.py +0 -0
  74. zotcli-0.2.1/tests/unit/test_resolvers/test_arxiv.py +136 -0
  75. zotcli-0.2.1/tests/unit/test_resolvers/test_crossref.py +103 -0
  76. zotcli-0.2.1/tests/unit/test_resolvers/test_crossref_search.py +135 -0
  77. zotcli-0.2.1/tests/unit/test_resolvers/test_ieee.py +217 -0
  78. zotcli-0.2.1/tests/unit/test_resolvers/test_openalex.py +171 -0
  79. zotcli-0.2.1/tests/unit/test_resolvers/test_openlibrary.py +96 -0
  80. zotcli-0.2.1/tests/unit/test_resolvers/test_pubmed.py +126 -0
  81. zotcli-0.2.1/tests/unit/test_resolvers/test_sciencedirect.py +174 -0
  82. zotcli-0.2.1/tests/unit/test_resolvers/test_semantic_scholar.py +169 -0
  83. zotcli-0.2.1/tests/unit/test_resolvers/test_unpaywall.py +235 -0
  84. zotcli-0.1.2/SKILL.md +0 -227
  85. zotcli-0.1.2/docs/commands.md +0 -182
  86. zotcli-0.1.2/src/zotcli/cli/main.py +0 -71
  87. zotcli-0.1.2/src/zotcli/config.py +0 -78
  88. {zotcli-0.1.2 → zotcli-0.2.1}/.github/workflows/docs.yml +0 -0
  89. {zotcli-0.1.2 → zotcli-0.2.1}/.github/workflows/publish.yml +0 -0
  90. {zotcli-0.1.2 → zotcli-0.2.1}/docs/api_reference.md +0 -0
  91. {zotcli-0.1.2 → zotcli-0.2.1}/docs/cli_reference.md +0 -0
  92. {zotcli-0.1.2 → zotcli-0.2.1}/docs/data_models.md +0 -0
  93. {zotcli-0.1.2 → zotcli-0.2.1}/docs/getting_started.md +0 -0
  94. {zotcli-0.1.2 → zotcli-0.2.1}/docs/index.md +0 -0
  95. {zotcli-0.1.2 → zotcli-0.2.1}/mkdocs.yml +0 -0
  96. {zotcli-0.1.2 → zotcli-0.2.1}/src/zotcli/__main__.py +0 -0
  97. {zotcli-0.1.2 → zotcli-0.2.1}/src/zotcli/cli/__init__.py +0 -0
  98. {zotcli-0.1.2 → zotcli-0.2.1}/src/zotcli/cli/attachments.py +0 -0
  99. {zotcli-0.1.2 → zotcli-0.2.1}/src/zotcli/cli/search.py +0 -0
  100. {zotcli-0.1.2 → zotcli-0.2.1}/src/zotcli/cli/stats.py +0 -0
  101. {zotcli-0.1.2 → zotcli-0.2.1}/src/zotcli/db.py +0 -0
  102. {zotcli-0.1.2 → zotcli-0.2.1}/src/zotcli/export/__init__.py +0 -0
  103. {zotcli-0.1.2 → zotcli-0.2.1}/src/zotcli/export/bibtex.py +0 -0
  104. {zotcli-0.1.2 → zotcli-0.2.1}/src/zotcli/export/csv_.py +0 -0
  105. {zotcli-0.1.2 → zotcli-0.2.1}/src/zotcli/export/json_.py +0 -0
  106. {zotcli-0.1.2 → zotcli-0.2.1}/src/zotcli/export/markdown.py +0 -0
  107. {zotcli-0.1.2 → zotcli-0.2.1}/src/zotcli/queries/__init__.py +0 -0
  108. {zotcli-0.1.2 → zotcli-0.2.1}/src/zotcli/queries/attachments.py +0 -0
  109. {zotcli-0.1.2 → zotcli-0.2.1}/src/zotcli/queries/collections.py +0 -0
  110. {zotcli-0.1.2 → zotcli-0.2.1}/src/zotcli/queries/items.py +0 -0
  111. {zotcli-0.1.2 → zotcli-0.2.1}/src/zotcli/queries/search.py +0 -0
  112. {zotcli-0.1.2 → zotcli-0.2.1}/src/zotcli/queries/tags.py +0 -0
  113. {zotcli-0.1.2 → zotcli-0.2.1}/test_script.py +0 -0
  114. {zotcli-0.1.2 → zotcli-0.2.1}/tests/__init__.py +0 -0
  115. {zotcli-0.1.2 → zotcli-0.2.1}/tests/conftest.py +0 -0
  116. {zotcli-0.1.2 → zotcli-0.2.1}/tests/test_cli.py +0 -0
  117. {zotcli-0.1.2 → zotcli-0.2.1}/tests/test_db.py +0 -0
  118. {zotcli-0.1.2 → zotcli-0.2.1}/tests/test_export.py +0 -0
  119. {zotcli-0.1.2 → zotcli-0.2.1}/tests/test_queries.py +0 -0
  120. {zotcli-0.1.2 → zotcli-0.2.1}/zot.png +0 -0
  121. {zotcli-0.1.2 → zotcli-0.2.1}/zotcli.code-workspace +0 -0
@@ -4,6 +4,12 @@
4
4
  PLAN.md
5
5
  Instructions.md
6
6
 
7
+ # Research checkout — Zotero upstream sources used only for lookup
8
+ zotero-sources-lookup/
9
+
10
+ # Self-contained zotcli runtime data (config / cookies / cache / logs)
11
+ .zotcli/
12
+
7
13
  site/
8
14
 
9
15
  tests/__pycache__/*
@@ -0,0 +1,112 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [0.2.0] — 2026-05-10
6
+
7
+ ### Added
8
+
9
+ - **`zot add` command group** — adds items to Zotero via its local connector HTTP server (port 23119). Zotero must be running; `zotero.sqlite` is never modified directly.
10
+ - **Smart auto-detect dispatcher** — bare `zot add "<anything>"` identifies DOI, arXiv ID, PMID, ISBN, IEEE/ScienceDirect URL, citation string, or local file path automatically.
11
+ - **`zot add doi`** — resolve via Crossref, send to Zotero. Supports `--collection`, `--tag`, `--dry-run`, `--with-pdf`, `--on-duplicate`.
12
+ - **`zot add arxiv`** — resolve via arXiv Atom feed → CSL-JSON.
13
+ - **`zot add pmid`** — resolve via NCBI eutils.
14
+ - **`zot add isbn`** — resolve via OpenLibrary (Google Books fallback).
15
+ - **`zot add cite`** — free-text citation string → Crossref bibliographic search → DOI; fallback OpenAlex then Semantic Scholar. Interactive disambiguation table when confidence is low. `--file` for batch citation files.
16
+ - **`zot add url`** — auto-routes arXiv/PubMed/IEEE/ScienceDirect/doi.org URLs to the relevant resolver; generic URLs fall back to `saveSnapshot`.
17
+ - **`zot add file`** — stream a local PDF or EPUB to `/connector/saveStandaloneAttachment`; polls for Zotero's `RecognizeDocument` result (configurable timeout, default 30 s).
18
+ - **`zot add import`** — POST raw RIS / BibTeX / CSL-JSON bytes to `/connector/import`.
19
+ - **`zot add batch`** — process a file of mixed inputs (one per line); summary table at end; non-zero exit if any line failed.
20
+ - **`zot add login`** — manage service credentials: Unpaywall (email), IEEE Xplore (browser SSO), ScienceDirect (browser SSO). `--reset` to clear. `--install-browser` for first-time Playwright Chromium install.
21
+ - **`--with-pdf` flag** on doi/arxiv/pmid/isbn/cite/url — attach an open-access PDF using Unpaywall; fall back to publisher cookies (IEEE/Elsevier) if browser-authenticated.
22
+ - **`zot add status`** — preflight check: reports Zotero reachability, selected collection, and connector URL.
23
+ - **`zot config` command group** — `get`, `set`, `path` subcommands for reading and writing `<zotcli-home>/config.toml`.
24
+ - **Self-contained data directory** (`<zotcli-home>`) — resolved via `ZOTCLI_HOME` env → SKILL.md sibling search → `~/.zotcli`. Holds `config.toml`, `credentials.json`, `cookies/`, `cache/`, `logs/`.
25
+ - **Write gate** — `write.enabled = false` by default. Enable once with `zot config set write.enabled true`, or pass `--allow-write` / set `ZOTCLI_ALLOW_WRITE=1` per command.
26
+ - **Duplicate detection** (report-only by default) — on duplicate DOI/arXiv, prints existing item key + title and exits 0; no mutation.
27
+ - **`--dry-run`** on all add subcommands — resolves metadata and prints the payload that would be sent, without contacting the connector.
28
+ - **`-v` / `--verbose`** — echoes every HTTP request and response to stderr.
29
+ - **`--non-interactive`** — suppresses all prompts; used in scripts and agent contexts.
30
+ - **Rotating log** at `<zotcli-home>/logs/zot.log` (1 MB × 3 backups).
31
+ - **Optional dependency groups**: `write = ["httpx>=0.27"]`, `browser = ["playwright>=1.40"]`, `all` aggregates all extras.
32
+ - **441 unit + integration tests** (up from 40 in v0.1.3). e2e tests are opt-in (`pytest -m e2e`).
33
+ - **`docs/architecture-write.md`** — maintainer-facing overview of the write-path design.
34
+
35
+ ### Changed
36
+
37
+ - `README.md` and `SKILL.md` updated to document write capability and correct the "never writes" framing. The accurate framing is: default is strictly read-only; writes require opt-in and go through Zotero's connector.
38
+ - `pyproject.toml` description updated; version bumped to 0.2.0.
39
+ - `docs/commands.md` extended with full `zot add` and `zot config` reference.
40
+ - SKILL.md `description:` frontmatter now advertises both reading and writing.
41
+
42
+ ### Fixed
43
+
44
+ - Click 8.2 `mix_stderr` keyword removal (M5 fix).
45
+ - `RotatingFileHandler` lock-pairing bug in logging setup (M5 fix).
46
+ - `updateSession` target correctly sent as flat string, not nested object (M2 fix).
47
+
48
+ ### Deferred to v0.3
49
+
50
+ - Edit / delete existing items — blocked on Zotero local API not yet supporting writes, or requires shipping a `.xpi` plugin. Tracked in `PLAN_WRITE.md §12`.
51
+ - Parallel connector calls in `zot add batch` (`--jobs N` accepted but no-op; sequential connector calls are safe; parallel resolver lookups deferred).
52
+ - Group library targeting beyond current-selection default.
53
+
54
+ ### Manual smoke test
55
+
56
+ Run the following commands against a live Zotero instance before tagging v0.2.0:
57
+
58
+ ```bash
59
+ # Sanity
60
+ zot --version
61
+ zot stats
62
+
63
+ # Write gate
64
+ zot config set write.enabled true
65
+ zot config get write.enabled # should print "true"
66
+ zot config path # should print <zotcli-home>
67
+
68
+ # Preflight
69
+ zot add status # should report Zotero as reachable
70
+
71
+ # Add by identifier
72
+ zot add doi 10.1038/s41586-020-2649-2 --collection Inbox --dry-run
73
+ zot add doi 10.1038/s41586-020-2649-2 --collection Inbox --tag smoke-test
74
+ zot add arxiv 2401.12345 --collection Preprints
75
+ zot add pmid 31452104
76
+ zot add isbn 978-0-262-03384-8 --collection Books
77
+
78
+ # Add by URL
79
+ zot add url https://ieeexplore.ieee.org/document/9876543
80
+ zot add url https://www.sciencedirect.com/science/article/pii/S2352467725000102
81
+
82
+ # Free-text citation
83
+ zot add cite "Zhang, J., Geth, F., Heidari, R., Verbič, G. (2025) Beyond simplifications: Evaluating assumptions for low-voltage network modelling in the DER era."
84
+
85
+ # Local file
86
+ zot add file ./paper.pdf --collection Inbox
87
+
88
+ # Import
89
+ zot add import refs.bib --collection "Imports/test"
90
+
91
+ # Batch
92
+ printf '10.1109/TPWRS.2023.1234567\n2401.12345\n' > /tmp/papers.txt
93
+ zot add batch /tmp/papers.txt --collection "Batch Test"
94
+
95
+ # Paywalled PDF (optional: requires prior login)
96
+ zot add login --service unpaywall
97
+ zot add doi 10.1038/s41586-020-2649-2 --with-pdf
98
+ ```
99
+
100
+ ---
101
+
102
+ ## [0.1.3] — 2026-04-13
103
+
104
+ ### Added
105
+
106
+ - Item export command (`zot export`) with JSON, CSV, BibTeX, and Markdown formats.
107
+ - Collection display improvements and recursive collection traversal.
108
+
109
+ ### Fixed
110
+
111
+ - Removed unsupported schema version warnings from schema validation.
112
+ - Database path auto-detection clarified for Linux, macOS, and Windows.
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: zotcli
3
- Version: 0.1.2
4
- Summary: A read-only CLI for browsing and exporting a Zotero library
3
+ Version: 0.2.1
4
+ Summary: A CLI for browsing, exporting, and adding items to a Zotero library
5
5
  Author-email: MohamedNumair <mo7amednumair@gmail.com>
6
6
  License: MIT
7
7
  Requires-Python: >=3.10
@@ -11,18 +11,29 @@ Requires-Dist: pydantic>=2.0
11
11
  Requires-Dist: python-dateutil>=2.9
12
12
  Requires-Dist: rich>=13.0
13
13
  Provides-Extra: all
14
+ Requires-Dist: httpx>=0.27; extra == 'all'
14
15
  Requires-Dist: jinja2>=3.1; extra == 'all'
16
+ Requires-Dist: playwright>=1.40; extra == 'all'
15
17
  Requires-Dist: pybtex>=0.24; extra == 'all'
16
18
  Provides-Extra: bibtex
17
19
  Requires-Dist: pybtex>=0.24; extra == 'bibtex'
20
+ Provides-Extra: browser
21
+ Requires-Dist: playwright>=1.40; extra == 'browser'
22
+ Provides-Extra: dev
23
+ Requires-Dist: pytest; extra == 'dev'
24
+ Requires-Dist: pytest-httpserver; extra == 'dev'
25
+ Requires-Dist: tomli-w; extra == 'dev'
26
+ Requires-Dist: vcrpy; extra == 'dev'
18
27
  Provides-Extra: export
19
28
  Requires-Dist: jinja2>=3.1; extra == 'export'
29
+ Provides-Extra: write
30
+ Requires-Dist: httpx>=0.27; extra == 'write'
20
31
  Description-Content-Type: text/markdown
21
32
 
22
33
  # zotcli (`zot`)
23
34
  ![alt text](zot.png)
24
35
 
25
- A "crazy" good read-only command-line interface for your local [Zotero](https://www.zotero.org/) library. Queries `zotero.sqlite` directly no Zotero app running, no API key, no internet required. Never writes to the database.
36
+ A command-line interface for your local [Zotero](https://www.zotero.org/) library. **Default: strictly read-only.** Write capabilities are opt-in and route through Zotero's own connector HTTP server — `zotero.sqlite` is **never** modified directly.
26
37
 
27
38
  **Library stats on this machine:** 3,771 items · 200 collections · 3,201 tags · 6.2 GB storage
28
39
 
@@ -31,7 +42,17 @@ A "crazy" good read-only command-line interface for your local [Zotero](https://
31
42
  ## Installation
32
43
 
33
44
  ```bash
45
+ # Read-only (default)
34
46
  pip install zotcli
47
+
48
+ # Adds the write API client (httpx) — required for any zot add … command
49
+ pip install "zotcli[write]"
50
+
51
+ # Adds Playwright for paywalled-PDF retrieval via browser SSO
52
+ pip install "zotcli[browser]"
53
+
54
+ # Everything
55
+ pip install "zotcli[all]"
35
56
  ```
36
57
 
37
58
  Verify:
@@ -41,7 +62,7 @@ zot --help
41
62
 
42
63
  The database at `~/Zotero/zotero.sqlite` is auto-detected on WSL. Override anytime with `--db PATH`.
43
64
 
44
- depending on the operating system the Zotero folder can be in different locations. By default, zotcli looks for the database at `~/Zotero/zotero.sqlite`, which works for most setups. for Windows users, the Zotero folder is typically located in `C:\Users\<YourUsername>\Zotero`. For macOS users, it is usually found in `~/Zotero`. If you have a custom setup or want to specify a different path, you can use the `--db` option to point zotcli to the correct location of your `zotero.sqlite` file.
65
+ Depending on the operating system the Zotero folder can be in different locations. By default, zotcli looks for the database at `~/Zotero/zotero.sqlite`, which works for most setups. For Windows users, the Zotero folder is typically located in `C:\Users\<YourUsername>\Zotero`. For macOS users, it is usually found in `~/Zotero`. If you have a custom setup or want to specify a different path, you can use the `--db` option to point zotcli to the correct location of your `zotero.sqlite` file.
45
66
 
46
67
  ---
47
68
 
@@ -50,24 +71,47 @@ depending on the operating system the Zotero folder can be in different location
50
71
  ```
51
72
  zotcli/
52
73
  ├── PLAN.md # Original design document
74
+ ├── PLAN_WRITE.md # Write-capability design document
53
75
  ├── SKILL.md # Agent skill descriptor
54
76
  ├── pyproject.toml # Package metadata and dependencies
55
77
  ├── docs/
56
- └── commands.md # Full command reference
78
+ ├── commands.md # Full command reference
79
+ │ └── architecture-write.md # Write-path architecture overview
57
80
  ├── src/
58
81
  │ └── zotcli/
59
82
  │ ├── __init__.py
60
83
  │ ├── __main__.py # python -m zotcli entrypoint
61
84
  │ ├── db.py # Read-only SQLite connection + auto-discovery
62
- │ ├── config.py # TOML config (~/.config/zotcli/config.toml)
85
+ │ ├── config.py # TOML config + [write]/[unpaywall]/[browser] sections
86
+ │ ├── paths.py # Cross-platform self-contained path resolution
63
87
  │ ├── models.py # Pydantic v2 models: Item, Collection, Creator, Attachment, Note
64
88
  │ ├── queries/
65
- │ │ ├── items.py # Core item fetch (_build_items — fields, creators, tags,
66
- │ │ │ # collections, attachments, notes in one go)
89
+ │ │ ├── items.py # Core item fetch
67
90
  │ │ ├── collections.py # Collection tree queries
68
91
  │ │ ├── attachments.py # Attachment path resolution helpers
69
92
  │ │ ├── tags.py # Tag queries
70
- │ │ └── search.py # Field search, author search, DOI, year, fulltext
93
+ │ │ └── search.py # Field search, author search, DOI, year, fulltext
94
+ │ ├── write/ # Write-path package (requires zotcli[write])
95
+ │ │ ├── connector_client.py # httpx client for /connector/*
96
+ │ │ ├── preflight.py # Zotero liveness check
97
+ │ │ ├── session.py # Session lifecycle + updateSession
98
+ │ │ ├── csl_json.py # CSL-JSON ↔ connector item shape
99
+ │ │ ├── identifiers.py # detect_kind: DOI / arXiv / PMID / ISBN / URL / citation
100
+ │ │ ├── dedup.py # Read-only duplicate check against DB
101
+ │ │ ├── citation_pipeline.py # Free-text citation → DOI pipeline
102
+ │ │ ├── pdf.py # MIME sniff + streaming upload
103
+ │ │ ├── browser.py # Playwright headed window (lazy import; requires zotcli[browser])
104
+ │ │ ├── credentials.py # File-based credential store (mode 0600)
105
+ │ │ └── resolvers/
106
+ │ │ ├── crossref.py # DOI → CSL-JSON; bibliographic search
107
+ │ │ ├── arxiv.py # arXiv ID → CSL-JSON
108
+ │ │ ├── pubmed.py # PMID → CSL-JSON
109
+ │ │ ├── openlibrary.py # ISBN → CSL-JSON
110
+ │ │ ├── openalex.py # Citation/title fallback
111
+ │ │ ├── semantic_scholar.py # Second fallback (rate-limited)
112
+ │ │ ├── unpaywall.py # DOI → OA PDF URL (opt-in)
113
+ │ │ ├── ieee.py # URL → DOI extraction helpers
114
+ │ │ └── sciencedirect.py # URL/PII → DOI extraction helpers
71
115
  │ ├── export/
72
116
  │ │ ├── json_.py # Full-fidelity JSON dump
73
117
  │ │ ├── csv_.py # Flat CSV (one row per item)
@@ -76,6 +120,8 @@ zotcli/
76
120
  │ └── cli/
77
121
  │ ├── main.py # Root Click group + global options
78
122
  │ ├── render.py # Shared Rich helpers (tables, panels, trees)
123
+ │ ├── add.py # `zot add` group + auto-detect dispatcher
124
+ │ ├── config_cmd.py # `zot config` group
79
125
  │ ├── collections.py # `zot collections` subcommands
80
126
  │ ├── items.py # `zot items` subcommands
81
127
  │ ├── attachments.py # `zot attachments` subcommands
@@ -84,10 +130,9 @@ zotcli/
84
130
  │ └── export.py # `zot export` subcommands
85
131
  └── tests/
86
132
  ├── conftest.py # In-memory SQLite fixture with seeded test data
87
- ├── test_db.py # Database layer tests
88
- ├── test_queries.py # Query layer tests (items, collections, search, tags)
89
- ├── test_export.py # Export format tests (JSON, CSV, BibTeX, Markdown)
90
- └── test_cli.py # CLI integration tests via CliRunner
133
+ ├── unit/ # Unit tests (no network, no Zotero)
134
+ ├── integration/ # Integration tests (mocked connector + resolvers)
135
+ └── e2e/ # End-to-end (opt-in; requires real Zotero)
91
136
  ```
92
137
 
93
138
  ---
@@ -102,9 +147,18 @@ Query layer (queries/)
102
147
  Database layer (db.py) ← read-only URI: file:zotero.sqlite?mode=ro
103
148
 
104
149
  zotero.sqlite
150
+
151
+ Write path (opt-in):
152
+ CLI layer (cli/add.py)
153
+ ↓ resolve identifiers via external APIs
154
+ Resolver pipeline (write/resolvers/)
155
+ ↓ CSL-JSON
156
+ Connector client (write/connector_client.py)
157
+ ↓ loopback HTTP
158
+ Zotero desktop app → zotero.sqlite + storage/
105
159
  ```
106
160
 
107
- Every item fetch runs 6 batched queries in one call — fields, creators, tags, collection memberships, attachments (with resolved absolute paths), and notes so all data is available everywhere without extra round-trips.
161
+ See [`docs/architecture-write.md`](docs/architecture-write.md) for the full write-path design.
108
162
 
109
163
  ---
110
164
 
@@ -114,6 +168,9 @@ These go **before** the subcommand:
114
168
 
115
169
  ```
116
170
  zot [--db PATH] [--library ID] [--format table|json|csv] [--no-color] <command>
171
+
172
+ Write-related globals (only relevant when write.enabled=true):
173
+ zot [--allow-write] [--connector-url URL] [--require-zotero/--no-require-zotero] <command>
117
174
  ```
118
175
 
119
176
  ---
@@ -296,68 +353,23 @@ zot search "bayesian" --field title
296
353
  │ │ │ │ for Transformer Tap Position Estimation │ │ │
297
354
  │ 2 │ 6BR3CYQA │ conferencePaper │ Bayesian distribution system state │ Angioni et al. │ 2016 │
298
355
  │ │ │ │ estimation in presence of non-Gaussian… │ │ │
299
- │ 3 │ K2YXXPX7 │ journalArticle │ A Recursive Bayesian Approach for │ Singh et al. │ 2010 │
300
- │ │ │ │ Identification of Network Configuration… │ │ │
301
- │ 4 │ CRJWMH2X │ journalArticle │ Real-Time Topology Estimation Using │ Liu et al. │ 2023 │
302
- │ │ │ │ Graph-Bank Transformer… │ │ │
303
- │ 5 │ TJH8CGUT │ conferencePaper │ Bayesian Methods for the Identification │ Brouillon │ 2021 │
304
- │ │ │ │ of Distribution Networks │ et al. │ │
305
356
  └────┴───────────┴─────────────────┴───────────────────────────────────────────┴────────────────┴────────┘
306
357
  ```
307
358
 
308
- **By author name** (queries the creators table, partial match):
359
+ **By author name:**
309
360
  ```bash
310
361
  zot search --author "Numair"
311
362
  ```
312
- ```
313
- 12 result(s)
314
- ┏━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━┓
315
- ┃ # ┃ Key ┃ Type ┃ Title ┃ Authors ┃ Year ┃
316
- ┡━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━┩
317
- │ 1 │ MDLX3G4P │ conferencePaper │ A Proposed IoT Architecture for Effective │ Numair et al. │ 2020 │
318
- │ │ │ │ Energy Management in Smart Microgrids │ │ │
319
- │ 2 │ W3P98AE9 │ conferencePaper │ On the UK smart metering system and value │ Numair et al. │ 2023 │
320
- │ │ │ │ of data for distribution system… │ │ │
321
- │ 3 │ UIMHSHNV │ journalArticle │ Fault Detection and Localisation in LV │ Numair et al. │ 2023 │
322
- │ │ │ │ Distribution Networks Using Smart Meter… │ │ │
323
- │ 4 │ TAVRNAY6 │ bookSection │ Infrastructure for the 4th Industrial │ Numair et al. │ 2024 │
324
- │ │ │ │ Revolution Technologies │ │ │
325
- │ 5 │ 5UFZMSLU │ journalArticle │ UNLOCKING DATA CENTRE HOSTING CAPACITY… │ Numair et al. │ 2026 │
326
- │ … │ … │ … │ … │ … │ … │
327
- └────┴───────────┴─────────────────┴───────────────────────────────────────────┴───────────────┴────────┘
328
- ```
329
363
 
330
364
  **By DOI:**
331
365
  ```bash
332
366
  zot search --doi "10.1016/j.epsr.2020.106394"
333
367
  ```
334
- ```
335
- 1 result(s)
336
- ┏━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━━━━┓
337
- ┃ # ┃ Key ┃ Type ┃ Title ┃ Authors ┃ Year ┃
338
- ┡━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━┩
339
- │ 1 │ ZGYGL35J │ journalArticle │ Preventative high impedance fault │ Langeroudi │ 2020 │
340
- │ │ │ │ detection using distribution system… │ et al. │ │
341
- └────┴───────────┴────────────────┴───────────────────────────────────────────┴──────────────────┴────────┘
342
- ```
343
368
 
344
369
  **By year range:**
345
370
  ```bash
346
371
  zot search --year 2023 --type conferencePaper
347
372
  ```
348
- ```
349
- 361 result(s) [truncated to first 5 shown]
350
- ┏━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━┓
351
- ┃ # ┃ Key ┃ Type ┃ Title ┃ Authors ┃ Year ┃
352
- ┡━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━┩
353
- │ 1 │ ZRF8IFPI │ journalArticle │ Fault Location Method for an Active │ Zhao et al. │ 2023 │
354
- │ │ │ │ Distribution Network Based on… │ │ │
355
- │ 2 │ G3USAIB9 │ journalArticle │ PMU Measurements-Based Short-Term │ Li et al. │ 2023 │
356
- │ │ │ │ Voltage Stability Assessment… │ │ │
357
- │ 3 │ RBUU6AM2 │ journalArticle │ New coordination framework for smart │ Hussain et al. │ 2023 │
358
- │ │ │ │ home peer-to-peer trading… │ │ │
359
- └────┴───────────┴─────────────────┴───────────────────────────────────────────┴─────────────────┴────────┘
360
- ```
361
373
 
362
374
  ---
363
375
 
@@ -371,20 +383,6 @@ zot attachments path 5UFZMSLU
371
383
  ~/Zotero/storage/RIB344FW/Numair et al. - 2026 - UNLOCKING DATA CENTRE HOSTING CAPACITY AND FLEXIBILITY THROUGH DYNAMIC CABLE RATING.pdf
372
384
  ```
373
385
 
374
- **List all attachments for an item (with existence check):**
375
- ```bash
376
- zot items attachments W3P98AE9
377
- ```
378
- ```
379
- Attachments for W3P98AE9
380
- ┏━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
381
- ┃ Key ┃ Type ┃ Mode ┃ Exists ┃ Path ┃
382
- ┡━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
383
- │ GKHSKIP4 │ text/html │ imported_url │ ✓ │ …/storage/GKHSKIP4/10136487.html │
384
- │ 7PZKGWIJ │ application/pdf │ imported_url │ ✓ │ …/storage/7PZKGWIJ/Numair et al. - 2023… │
385
- └──────────┴─────────────────┴──────────────┴────────┴───────────────────────────────────────────┘
386
- ```
387
-
388
386
  **Find all missing attachments:**
389
387
  ```bash
390
388
  zot attachments list --missing
@@ -393,7 +391,6 @@ zot attachments list --missing
393
391
  **Open a PDF in the system viewer:**
394
392
  ```bash
395
393
  zot attachments open 5UFZMSLU
396
- # Opening: ~/Zotero/storage/RIB344FW/Numair et al. - 2026 - ...pdf
397
394
  ```
398
395
 
399
396
  ---
@@ -403,76 +400,20 @@ zot attachments open 5UFZMSLU
403
400
  **BibTeX:**
404
401
  ```bash
405
402
  zot export bib --collection "Energy Market" --output refs.bib
406
- ```
407
- ```bibtex
408
- @article{hussain_new_2023,
409
- title = {New coordination framework for smart home peer-to-peer trading…},
410
- author = {Hussain, Sadam and Azim, M. Imran and Lai, Chunyan and Eicker, Ursula},
411
- year = {2023},
412
- journal = {Energy},
413
- volume = {284},
414
- pages = {129297},
415
- doi = {10.1016/j.energy.2023.129297},
416
- }
417
-
418
- @article{tsaousoglou_integrating_2023,
419
- title = {Integrating Distributed Flexibility into TSO-DSO Coordinated Electricity Markets},
420
- author = {Tsaousoglou, Georgios and Junker, Rune and …},
421
- year = {2023},
422
- doi = {10.1109/TEMPR.2023.3319673},
423
- }
424
-
425
- ```
426
-
427
- **CSV:**
428
- ```bash
429
- zot export csv --collection "Energy Market" --output refs.csv
430
- ```
431
- ```
432
- item_id,key,item_type,title,year,doi,journal,…,author_1,author_2,…,tags
433
- 10,RBUU6AM2,journalArticle,New coordination framework…,2023,10.1016/…,Energy,…,"Hussain, Sadam","Azim, M. Imran",…,Smart grid;Flexibility
434
- 18,8DE2V7ZZ,journalArticle,Integrating Distributed Flexibility…,2023,10.1109/…,…
403
+ zot export bib --item 5UFZMSLU --output ref.bib
435
404
  ```
436
405
 
437
- **JSON** (includes fully resolved attachment paths and notes):
406
+ **CSV / JSON / Markdown:**
438
407
  ```bash
439
- zot export json --collection "Energy Market" --output refs.json
440
- ```
441
- ```json
442
- [
443
- {
444
- "item_id": 10,
445
- "key": "RBUU6AM2",
446
- "item_type": "journalArticle",
447
- "title": "New coordination framework for smart home peer-to-peer trading…",
448
- "year": "2023",
449
- "attachments": [
450
- {
451
- "key": "PXW7M26P",
452
- "content_type": "application/pdf",
453
- "file_exists": true,
454
- "absolute_path": "~/Zotero/storage/PXW7M26P/Hussain et al. - 2023 - …pdf"
455
- }
456
- ],
457
- "notes": [],
458
- "tags": ["Smart grid", "Distribution transformer", "Flexibility"],
459
-
460
- }
461
- ]
462
- ```
463
-
464
- **Markdown:**
465
- ```bash
466
- zot export markdown --collection "Energy Market" --output refs.md
467
- zot export markdown --all --notes --output full-library.md # include notes sections
408
+ zot export csv --collection "Energy Market" --output refs.csv
409
+ zot export json --all --output library.json
410
+ zot export markdown --all --notes --output report.md
468
411
  ```
469
412
 
470
413
  ---
471
414
 
472
415
  ### Workflow: search → get attachment paths
473
416
 
474
- Find all papers with "bayesian" in the title **or** authored by "Numair", then print the PDF path for each:
475
-
476
417
  ```python
477
418
  from zotcli.db import ZoteroDatabase
478
419
  from zotcli.queries.search import search_items, search_by_author
@@ -493,13 +434,87 @@ with ZoteroDatabase(DB) as db:
493
434
  print(f"{item.key}\t{att.absolute_path}")
494
435
  ```
495
436
 
437
+ ---
438
+
439
+ ## Writing to your library
440
+
441
+ > **Default: strictly read-only.** Write capabilities are opt-in and route through Zotero's own connector HTTP server — `zotero.sqlite` is **never** modified directly.
442
+
443
+ **Zotero must be running** for any `zot add …` command to succeed.
444
+
445
+ ### Enable write capability
446
+
447
+ ```bash
448
+ # One-time setup (persists to config)
449
+ zot config set write.enabled true
450
+
451
+ # Check current status
452
+ zot config get write.enabled
453
+
454
+ # Verify Zotero is reachable
455
+ zot add status
496
456
  ```
497
- LVTG4KLQ ~/Zotero/storage/LVTG4KLQ/Chen2013_BayesianTap.pdf
498
- 5UFZMSLU ~/Zotero/storage/RIB344FW/Numair et al. - 2026 - UNLOCKING DATA CENTRE…pdf
499
- W3P98AE9 ~/Zotero/storage/7PZKGWIJ/Numair et al. - 2023 - On the UK smart metering…pdf
500
-
457
+
458
+ ### Quick-start: add items
459
+
460
+ ```bash
461
+ # Add by DOI / arXiv / PMID / ISBN
462
+ zot add doi 10.1109/TPWRS.2023.1234567
463
+ zot add arxiv 2401.12345 --collection Preprints
464
+ zot add pmid 31452104
465
+ zot add isbn 978-0-262-03384-8 --collection Books
466
+
467
+ # IEEE Xplore or ScienceDirect URL (DOI is extracted automatically — no browser needed)
468
+ zot add "https://ieeexplore.ieee.org/document/9876543"
469
+ zot add "https://www.sciencedirect.com/science/article/pii/S2352467725000XYZ"
470
+
471
+ # Free-text citation string
472
+ zot add "Zhang, J., Geth, F., Heidari, R., Verbič, G. (2025) Beyond simplifications…"
473
+
474
+ # Local PDF (Zotero auto-recognises the parent reference)
475
+ zot add ~/Downloads/paper.pdf
476
+
477
+ # Smart auto-detect: zot add figures out the type automatically
478
+ zot add "10.1109/TPWRS.2023.1234567" # detected as DOI
479
+ zot add "2401.12345" # detected as arXiv
480
+ zot add "/home/me/paper.pdf" # detected as file
481
+ ```
482
+
483
+ ### Batch add
484
+
485
+ ```bash
486
+ # papers.txt: one DOI / arXiv / URL / citation per line; # = comment
487
+ zot add batch papers.txt --collection "Smart Grid" --tag imported
488
+ ```
489
+
490
+ ### Import from a bibliography file
491
+
492
+ ```bash
493
+ # .bib, .ris, or .json (CSL-JSON)
494
+ zot add import refs.bib --collection "Imports/2026-05"
501
495
  ```
502
496
 
497
+ ### Optional: paywalled-PDF retrieval
498
+
499
+ ```bash
500
+ # Step 1: register for Unpaywall (email only; opt-in)
501
+ zot add login --service unpaywall
502
+
503
+ # Step 2: add with open-access PDF
504
+ zot add doi 10.1109/X --with-pdf
505
+
506
+ # For paywalled content: authenticate via browser SSO (requires zotcli[browser])
507
+ zot add login --service ieee # opens headed Chromium; sign in once
508
+ zot add login --service sciencedirect
509
+ zot add doi 10.1109/X --with-pdf # re-uses saved cookies
510
+ ```
511
+
512
+ ### Architecture summary
513
+
514
+ All writes go through `POST /connector/saveItems` (and related endpoints) on Zotero's local HTTP server at `127.0.0.1:23119`. Zotero performs every database transaction. `zotero.sqlite` is never opened in write mode by zotcli.
515
+
516
+ See [`docs/architecture-write.md`](docs/architecture-write.md) for full details.
517
+
503
518
  ---
504
519
 
505
520
  ## Running tests
@@ -508,33 +523,46 @@ W3P98AE9 ~/Zotero/storage/7PZKGWIJ/Numair et al. - 2023 - On the UK smart met
508
523
  python3 -m pytest tests/ -q
509
524
  ```
510
525
  ```
511
- ........................................
512
- 40 passed in 31s
526
+ 441 passed in Xs
513
527
  ```
514
528
 
515
- Tests use an in-memory SQLite fixture seeded with synthetic Zotero data. No real database needed.
529
+ Tests use an in-memory SQLite fixture seeded with synthetic Zotero data. No real database needed. e2e tests (requiring a live Zotero) are opt-in:
530
+
531
+ ```bash
532
+ python3 -m pytest tests/e2e -m e2e
533
+ ```
516
534
 
517
535
  ---
518
536
 
519
537
  ## Configuration
520
538
 
521
- Optional persistent config at `~/.config/zotcli/config.toml`:
539
+ Self-contained config at `<zotcli-home>/config.toml`. Run `zot config path` to find the directory.
522
540
 
523
541
  ```toml
524
542
  [database]
525
- path = "~/Zotero/zotero.sqlite"
526
- library_id = 1
543
+ path = "" # empty = auto-detect zotero.sqlite
527
544
 
528
- [output]
529
- default_format = "table"
530
- color = true
531
- page_size = 50
545
+ [write]
546
+ enabled = false # opt-in; set true once with: zot config set write.enabled true
547
+ connector_url = "http://127.0.0.1:23119"
548
+ require_zotero = true
549
+
550
+ [unpaywall]
551
+ enabled = false # opt-in; set up with: zot add login --service unpaywall
552
+ email = ""
553
+
554
+ [browser]
555
+ headless = false # SSO/captcha needs headed browser
532
556
  ```
533
557
 
558
+ Override `<zotcli-home>` with the `ZOTCLI_HOME` environment variable.
559
+
534
560
  ---
535
561
 
536
562
  ## Safety
537
563
 
538
- - Database opened with `sqlite3://…?mode=ro` — the OS-level read-only URI flag makes writes impossible
564
+ - Database opened with `sqlite3://…?mode=ro` — the OS-level read-only URI flag makes direct writes impossible
539
565
  - WAL journal detection warns if Zotero is currently open (pending writes may not be visible yet)
540
- - No network calls, no Zotero API, no authentication
566
+ - Write operations route through Zotero's own connector HTTP server — never via direct SQLite mutation
567
+ - Default is read-only; writes require explicit opt-in (`zot config set write.enabled true`)
568
+ - No Zotero API key required