pynakes 0.5.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 (183) hide show
  1. pynakes-0.5.1/.pre-commit-hooks.yaml +49 -0
  2. pynakes-0.5.1/LICENSE +21 -0
  3. pynakes-0.5.1/MANIFEST.in +4 -0
  4. pynakes-0.5.1/PKG-INFO +242 -0
  5. pynakes-0.5.1/README.md +197 -0
  6. pynakes-0.5.1/pyproject.toml +98 -0
  7. pynakes-0.5.1/setup.cfg +4 -0
  8. pynakes-0.5.1/src/pynakes/__init__.py +8 -0
  9. pynakes-0.5.1/src/pynakes/__main__.py +6 -0
  10. pynakes-0.5.1/src/pynakes/_calendar.py +58 -0
  11. pynakes-0.5.1/src/pynakes/_engine_groups.py +88 -0
  12. pynakes-0.5.1/src/pynakes/_engine_helpers.py +741 -0
  13. pynakes-0.5.1/src/pynakes/_engine_keys.py +92 -0
  14. pynakes-0.5.1/src/pynakes/_engine_ops.py +564 -0
  15. pynakes-0.5.1/src/pynakes/_identifiers.py +69 -0
  16. pynakes-0.5.1/src/pynakes/_text_utils.py +163 -0
  17. pynakes-0.5.1/src/pynakes/authors.py +270 -0
  18. pynakes-0.5.1/src/pynakes/batch.py +165 -0
  19. pynakes-0.5.1/src/pynakes/bibtex_parser.py +386 -0
  20. pynakes-0.5.1/src/pynakes/bibtex_writer.py +182 -0
  21. pynakes-0.5.1/src/pynakes/canonical.py +431 -0
  22. pynakes-0.5.1/src/pynakes/capabilities.py +345 -0
  23. pynakes-0.5.1/src/pynakes/cli.py +217 -0
  24. pynakes-0.5.1/src/pynakes/cli_commands/__init__.py +1 -0
  25. pynakes-0.5.1/src/pynakes/cli_commands/_fetch_report.py +39 -0
  26. pynakes-0.5.1/src/pynakes/cli_commands/_reference.py +129 -0
  27. pynakes-0.5.1/src/pynakes/cli_commands/add.py +150 -0
  28. pynakes-0.5.1/src/pynakes/cli_commands/batch.py +87 -0
  29. pynakes-0.5.1/src/pynakes/cli_commands/capabilities.py +34 -0
  30. pynakes-0.5.1/src/pynakes/cli_commands/convert.py +185 -0
  31. pynakes-0.5.1/src/pynakes/cli_commands/dedupe.py +116 -0
  32. pynakes-0.5.1/src/pynakes/cli_commands/edit.py +105 -0
  33. pynakes-0.5.1/src/pynakes/cli_commands/fetch.py +221 -0
  34. pynakes-0.5.1/src/pynakes/cli_commands/fields.py +206 -0
  35. pynakes-0.5.1/src/pynakes/cli_commands/files.py +110 -0
  36. pynakes-0.5.1/src/pynakes/cli_commands/format.py +217 -0
  37. pynakes-0.5.1/src/pynakes/cli_commands/groups.py +355 -0
  38. pynakes-0.5.1/src/pynakes/cli_commands/import_ref.py +176 -0
  39. pynakes-0.5.1/src/pynakes/cli_commands/init.py +302 -0
  40. pynakes-0.5.1/src/pynakes/cli_commands/inspect.py +115 -0
  41. pynakes-0.5.1/src/pynakes/cli_commands/integrity.py +173 -0
  42. pynakes-0.5.1/src/pynakes/cli_commands/keys.py +457 -0
  43. pynakes-0.5.1/src/pynakes/cli_commands/lint.py +68 -0
  44. pynakes-0.5.1/src/pynakes/cli_commands/metadata.py +257 -0
  45. pynakes-0.5.1/src/pynakes/cli_commands/normalize.py +206 -0
  46. pynakes-0.5.1/src/pynakes/cli_commands/remove.py +86 -0
  47. pynakes-0.5.1/src/pynakes/cli_commands/search.py +86 -0
  48. pynakes-0.5.1/src/pynakes/cli_commands/setops.py +360 -0
  49. pynakes-0.5.1/src/pynakes/cli_commands/show.py +48 -0
  50. pynakes-0.5.1/src/pynakes/cli_commands/tex.py +277 -0
  51. pynakes-0.5.1/src/pynakes/cli_commands/used.py +142 -0
  52. pynakes-0.5.1/src/pynakes/cli_common.py +543 -0
  53. pynakes-0.5.1/src/pynakes/cli_discovery.py +326 -0
  54. pynakes-0.5.1/src/pynakes/convert.py +270 -0
  55. pynakes-0.5.1/src/pynakes/dedupe.py +449 -0
  56. pynakes-0.5.1/src/pynakes/diff.py +32 -0
  57. pynakes-0.5.1/src/pynakes/editing.py +421 -0
  58. pynakes-0.5.1/src/pynakes/engine.py +449 -0
  59. pynakes-0.5.1/src/pynakes/fetch.py +695 -0
  60. pynakes-0.5.1/src/pynakes/fetch_progress.py +35 -0
  61. pynakes-0.5.1/src/pynakes/fields.py +280 -0
  62. pynakes-0.5.1/src/pynakes/files.py +222 -0
  63. pynakes-0.5.1/src/pynakes/filestore.py +913 -0
  64. pynakes-0.5.1/src/pynakes/formatters/__init__.py +91 -0
  65. pynakes-0.5.1/src/pynakes/formatters/_case.py +254 -0
  66. pynakes-0.5.1/src/pynakes/formatters/_date.py +91 -0
  67. pynakes-0.5.1/src/pynakes/formatters/_extra.py +130 -0
  68. pynakes-0.5.1/src/pynakes/formatters/_latex.py +494 -0
  69. pynakes-0.5.1/src/pynakes/formatters/_latex_tables.py +154 -0
  70. pynakes-0.5.1/src/pynakes/formatters/_pages.py +32 -0
  71. pynakes-0.5.1/src/pynakes/formatters/_registry.py +87 -0
  72. pynakes-0.5.1/src/pynakes/formatters/_units.py +53 -0
  73. pynakes-0.5.1/src/pynakes/group_tree.py +624 -0
  74. pynakes-0.5.1/src/pynakes/groups.py +73 -0
  75. pynakes-0.5.1/src/pynakes/importer.py +469 -0
  76. pynakes-0.5.1/src/pynakes/inheritance.py +155 -0
  77. pynakes-0.5.1/src/pynakes/initialize.py +264 -0
  78. pynakes-0.5.1/src/pynakes/integrity.py +679 -0
  79. pynakes-0.5.1/src/pynakes/interchange/__init__.py +75 -0
  80. pynakes-0.5.1/src/pynakes/interchange/_shared.py +262 -0
  81. pynakes-0.5.1/src/pynakes/interchange/csl.py +170 -0
  82. pynakes-0.5.1/src/pynakes/interchange/csv.py +72 -0
  83. pynakes-0.5.1/src/pynakes/interchange/endnote.py +171 -0
  84. pynakes-0.5.1/src/pynakes/interchange/mods.py +328 -0
  85. pynakes-0.5.1/src/pynakes/interchange/ris.py +152 -0
  86. pynakes-0.5.1/src/pynakes/io.py +187 -0
  87. pynakes-0.5.1/src/pynakes/journals.py +517 -0
  88. pynakes-0.5.1/src/pynakes/keys.py +363 -0
  89. pynakes-0.5.1/src/pynakes/lint.py +673 -0
  90. pynakes-0.5.1/src/pynakes/metadata/__init__.py +196 -0
  91. pynakes-0.5.1/src/pynakes/metadata/core.py +588 -0
  92. pynakes-0.5.1/src/pynakes/metadata/jabref.py +888 -0
  93. pynakes-0.5.1/src/pynakes/metadata/schema.py +190 -0
  94. pynakes-0.5.1/src/pynakes/model.py +578 -0
  95. pynakes-0.5.1/src/pynakes/normalize.py +540 -0
  96. pynakes-0.5.1/src/pynakes/providers/__init__.py +1 -0
  97. pynakes-0.5.1/src/pynakes/providers/_http.py +255 -0
  98. pynakes-0.5.1/src/pynakes/providers/arxiv.py +133 -0
  99. pynakes-0.5.1/src/pynakes/providers/crossref.py +78 -0
  100. pynakes-0.5.1/src/pynakes/providers/doi.py +31 -0
  101. pynakes-0.5.1/src/pynakes/providers/identity.py +48 -0
  102. pynakes-0.5.1/src/pynakes/providers/openalex.py +122 -0
  103. pynakes-0.5.1/src/pynakes/providers/pdf_overrides.json +106 -0
  104. pynakes-0.5.1/src/pynakes/providers/pdf_overrides.py +61 -0
  105. pynakes-0.5.1/src/pynakes/providers/publisher.py +111 -0
  106. pynakes-0.5.1/src/pynakes/providers/semantic_scholar.py +45 -0
  107. pynakes-0.5.1/src/pynakes/py.typed +0 -0
  108. pynakes-0.5.1/src/pynakes/search.py +222 -0
  109. pynakes-0.5.1/src/pynakes/setops.py +212 -0
  110. pynakes-0.5.1/src/pynakes/usage.py +378 -0
  111. pynakes-0.5.1/src/pynakes.egg-info/PKG-INFO +242 -0
  112. pynakes-0.5.1/src/pynakes.egg-info/SOURCES.txt +181 -0
  113. pynakes-0.5.1/src/pynakes.egg-info/dependency_links.txt +1 -0
  114. pynakes-0.5.1/src/pynakes.egg-info/entry_points.txt +2 -0
  115. pynakes-0.5.1/src/pynakes.egg-info/requires.txt +18 -0
  116. pynakes-0.5.1/src/pynakes.egg-info/top_level.txt +1 -0
  117. pynakes-0.5.1/tests/__init__.py +0 -0
  118. pynakes-0.5.1/tests/agent_eval/__init__.py +1 -0
  119. pynakes-0.5.1/tests/agent_eval/providers.py +147 -0
  120. pynakes-0.5.1/tests/agent_eval/runner.py +320 -0
  121. pynakes-0.5.1/tests/agent_eval/schemas.py +155 -0
  122. pynakes-0.5.1/tests/agent_eval/workspace.py +87 -0
  123. pynakes-0.5.1/tests/conftest.py +32 -0
  124. pynakes-0.5.1/tests/fixtures/biblatex_sample.bib +45 -0
  125. pynakes-0.5.1/tests/fixtures/bibtex_classic.bib +50 -0
  126. pynakes-0.5.1/tests/fixtures/conformance/biblatex-3.21-core.bib +31 -0
  127. pynakes-0.5.1/tests/fixtures/conformance/bibtex-0.99d-core.bib +31 -0
  128. pynakes-0.5.1/tests/fixtures/conformance/bibtex-regression.bib +55 -0
  129. pynakes-0.5.1/tests/fixtures/conformance/manifest.json +49 -0
  130. pynakes-0.5.1/tests/fixtures/conformance/upstream/biblatex-3.21-examples.bib +1663 -0
  131. pynakes-0.5.1/tests/fixtures/conformance/upstream/bibtex-0.99d-xampl.bib +362 -0
  132. pynakes-0.5.1/tests/fixtures/duplicate_entries.bib +51 -0
  133. pynakes-0.5.1/tests/fixtures/jabref_groups.bib +46 -0
  134. pynakes-0.5.1/tests/fixtures/linked_files.bib +43 -0
  135. pynakes-0.5.1/tests/fixtures/paper.aux +6 -0
  136. pynakes-0.5.1/tests/fixtures/paper.tex +11 -0
  137. pynakes-0.5.1/tests/fixtures/simple.bib +41 -0
  138. pynakes-0.5.1/tests/test_agent_beta_eval.py +72 -0
  139. pynakes-0.5.1/tests/test_batch.py +176 -0
  140. pynakes-0.5.1/tests/test_bibtex_parser.py +516 -0
  141. pynakes-0.5.1/tests/test_bibtex_writer.py +345 -0
  142. pynakes-0.5.1/tests/test_canonical.py +412 -0
  143. pynakes-0.5.1/tests/test_capabilities.py +130 -0
  144. pynakes-0.5.1/tests/test_cli.py +3359 -0
  145. pynakes-0.5.1/tests/test_conformance_corpus.py +163 -0
  146. pynakes-0.5.1/tests/test_convert.py +244 -0
  147. pynakes-0.5.1/tests/test_dedupe.py +300 -0
  148. pynakes-0.5.1/tests/test_diff.py +26 -0
  149. pynakes-0.5.1/tests/test_editing.py +133 -0
  150. pynakes-0.5.1/tests/test_engine.py +435 -0
  151. pynakes-0.5.1/tests/test_error_recovery.py +67 -0
  152. pynakes-0.5.1/tests/test_fetch.py +627 -0
  153. pynakes-0.5.1/tests/test_fields.py +224 -0
  154. pynakes-0.5.1/tests/test_files.py +144 -0
  155. pynakes-0.5.1/tests/test_filestore.py +352 -0
  156. pynakes-0.5.1/tests/test_gate_checks.py +177 -0
  157. pynakes-0.5.1/tests/test_group_tree.py +1251 -0
  158. pynakes-0.5.1/tests/test_groups.py +82 -0
  159. pynakes-0.5.1/tests/test_importer.py +435 -0
  160. pynakes-0.5.1/tests/test_inheritance.py +204 -0
  161. pynakes-0.5.1/tests/test_init.py +284 -0
  162. pynakes-0.5.1/tests/test_integrity.py +324 -0
  163. pynakes-0.5.1/tests/test_interchange.py +204 -0
  164. pynakes-0.5.1/tests/test_io.py +291 -0
  165. pynakes-0.5.1/tests/test_jabref_compatibility.py +538 -0
  166. pynakes-0.5.1/tests/test_jabref_metadata.py +897 -0
  167. pynakes-0.5.1/tests/test_jabref_parity.py +620 -0
  168. pynakes-0.5.1/tests/test_journals.py +165 -0
  169. pynakes-0.5.1/tests/test_keys.py +306 -0
  170. pynakes-0.5.1/tests/test_latex_utils.py +23 -0
  171. pynakes-0.5.1/tests/test_lint.py +515 -0
  172. pynakes-0.5.1/tests/test_metadata_schema.py +358 -0
  173. pynakes-0.5.1/tests/test_model.py +336 -0
  174. pynakes-0.5.1/tests/test_normalize.py +403 -0
  175. pynakes-0.5.1/tests/test_paren_operations.py +297 -0
  176. pynakes-0.5.1/tests/test_property.py +138 -0
  177. pynakes-0.5.1/tests/test_providers.py +210 -0
  178. pynakes-0.5.1/tests/test_reference_interactive.py +145 -0
  179. pynakes-0.5.1/tests/test_search.py +122 -0
  180. pynakes-0.5.1/tests/test_setops.py +526 -0
  181. pynakes-0.5.1/tests/test_stress.py +57 -0
  182. pynakes-0.5.1/tests/test_usage.py +209 -0
  183. pynakes-0.5.1/tests/test_workflows.py +107 -0
@@ -0,0 +1,49 @@
1
+ # pynakes pre-commit hooks.
2
+ #
3
+ # Add to a repository's .pre-commit-config.yaml:
4
+ #
5
+ # repos:
6
+ # - repo: https://github.com/maiani/pynakes
7
+ # rev: v0.5.0
8
+ # hooks:
9
+ # - id: pynakes-lint
10
+ # - id: pynakes-keys-check
11
+ # - id: pynakes-files-check # opt-in: validates linked-file paths
12
+ # - id: pynakes-dedupe-check # opt-in: flags duplicate works
13
+ #
14
+ # Every hook is read-only, offline, and deterministic. pre-commit passes the
15
+ # staged .bib files as arguments; --strict makes a finding fail the commit.
16
+
17
+ - id: pynakes-lint
18
+ name: pynakes lint
19
+ description: Validate BibTeX entries and fail on errors (duplicate/empty keys, missing required fields).
20
+ entry: pynakes lint --strict
21
+ language: python
22
+ files: \.bib$
23
+ types: [file]
24
+
25
+ - id: pynakes-keys-check
26
+ name: pynakes keys check
27
+ description: Fail if any citation key is duplicated.
28
+ entry: pynakes keys check --strict
29
+ language: python
30
+ files: \.bib$
31
+ types: [file]
32
+
33
+ - id: pynakes-files-check
34
+ name: pynakes files check
35
+ description: Fail if any JabRef linked file is missing or the wrong type.
36
+ entry: pynakes files check --strict
37
+ language: python
38
+ files: \.bib$
39
+ types: [file]
40
+ stages: [manual]
41
+
42
+ - id: pynakes-dedupe-check
43
+ name: pynakes dedupe check
44
+ description: Fail if duplicate works (same DOI/arXiv/title) are present.
45
+ entry: pynakes dedupe check --strict
46
+ language: python
47
+ files: \.bib$
48
+ types: [file]
49
+ stages: [manual]
pynakes-0.5.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Andrea Maiani
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,4 @@
1
+ # Ship the complete test environment with the source distribution so downstream
2
+ # packagers can validate the archive without checking out the repository.
3
+ include .pre-commit-hooks.yaml
4
+ recursive-include tests *.py *.bib *.json *.tex *.aux
pynakes-0.5.1/PKG-INFO ADDED
@@ -0,0 +1,242 @@
1
+ Metadata-Version: 2.4
2
+ Name: pynakes
3
+ Version: 0.5.1
4
+ Summary: Agent-friendly, round-trip-faithful BibTeX/BibLaTeX maintenance toolkit
5
+ Author-email: Andrea Maiani <dev@andreamaiani.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/maiani/pynakes
8
+ Project-URL: Repository, https://github.com/maiani/pynakes
9
+ Project-URL: Issues, https://github.com/maiani/pynakes/issues
10
+ Project-URL: Changelog, https://github.com/maiani/pynakes/blob/main/CHANGELOG.md
11
+ Keywords: bibtex,biblatex,bibliography,citations,references,jabref,latex,doi,reference-management,cli,agent
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Scientific/Engineering
22
+ Classifier: Topic :: Text Processing :: Markup :: LaTeX
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.11
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: click>=8.1.0
29
+ Requires-Dist: httpx>=0.27.0
30
+ Requires-Dist: typer>=0.12.0
31
+ Requires-Dist: rich>=13.7.0
32
+ Provides-Extra: dev
33
+ Requires-Dist: pre-commit>=3.6.0; extra == "dev"
34
+ Requires-Dist: pytest>=7.4.0; extra == "dev"
35
+ Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
36
+ Requires-Dist: hypothesis>=6.100.0; extra == "dev"
37
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
38
+ Requires-Dist: build>=1.2.0; extra == "dev"
39
+ Requires-Dist: twine>=5.0.0; extra == "dev"
40
+ Provides-Extra: docs
41
+ Requires-Dist: mkdocs>=1.6; extra == "docs"
42
+ Requires-Dist: mkdocs-material>=9.5; extra == "docs"
43
+ Requires-Dist: mkdocstrings[python]>=0.26; extra == "docs"
44
+ Dynamic: license-file
45
+
46
+ # pynakes
47
+
48
+ <p align="center">
49
+ <img src="https://raw.githubusercontent.com/maiani/pynakes/main/docs/assets/logo.svg" alt="pynakes logo" width="128">
50
+ </p>
51
+
52
+ [![PyPI](https://img.shields.io/pypi/v/pynakes.svg)](https://pypi.org/project/pynakes/)
53
+ [![Python](https://img.shields.io/pypi/pyversions/pynakes.svg)](https://pypi.org/project/pynakes/)
54
+ [![CI](https://img.shields.io/github/actions/workflow/status/maiani/pynakes/ci.yml?branch=main)](https://github.com/maiani/pynakes/actions)
55
+ [![License](https://img.shields.io/github/license/maiani/pynakes)](https://github.com/maiani/pynakes/blob/main/LICENSE)
56
+
57
+ > [!WARNING]
58
+ > `pynakes` is alpha-quality software. Interfaces and behavior may change between releases.
59
+
60
+ `pynakes` is a deterministic bibliography maintenance engine built for reliable agentic workflows, CI, and human use. Agents decide what should change; pynakes inspects, validates, previews, and applies those changes safely and reproducibly.
61
+
62
+ The Python CLI makes small, explicit, reviewable changes to `.bib` files —
63
+ minimal diffs, dry-run previews, and atomic writes — and reports every change
64
+ as structured JSON.
65
+
66
+ Requires Python 3.11+ and is packaged as an OS-independent CLI and library.
67
+
68
+ Untouched entries write back byte-for-byte — no hidden reformatting, reordering, or re-quoting. The file stays diff-friendly in git and yours for decades.
69
+
70
+ Named after the *Pinakes*, Callimachus's catalog of the Library of Alexandria — antiquity's first bibliography.
71
+ A `.bib` file is the index card; a **pinax** is the card together with the shelf it points at.
72
+
73
+ ## Why pynakes
74
+
75
+ - **Reviewable by design.** Modifying commands support `--dry-run --diff` for review before writing. Writes are atomic and re-parse-validated; `--backup` optionally retains the previous file. Ambiguous cases — conflicting merges, duplicate DOIs — are reported with exit code `2` rather than guessed.
76
+ - **Round-trip fidelity.** An entry you don't touch is written back byte-for-byte. pynakes never normalizes whitespace, reorders fields, or re-quotes values behind your back — so diffs stay tiny and reviewable.
77
+ - **A deterministic execution layer for agents and CI.** Stable JSON output and exit codes, machine-readable `capabilities`, structured change plans, and `--strict` / pre-commit gates let an agent exercise judgment without directly rewriting bibliography text.
78
+ - **Deterministic and offline by default.** No hidden time, randomness, or ordering; network access is explicit (`ref import`, `asset fetch`, or `--online`) and confined to the few commands that need it.
79
+ - **Preservation-first interoperability.** Untouched BibTeX/BibLaTeX source and
80
+ JabRef metadata round-trip unchanged. `pynakes-meta` holds native settings;
81
+ JabRef metadata can be preserved, adopted, and projected explicitly.
82
+
83
+ ## Two workflows
84
+
85
+ ### 1. Bibliography engine
86
+ Load any `.bib` file the BibTeX/BibLaTeX toolchain produces,
87
+ then:
88
+
89
+ - **Inspect** — entry count, encoding, duplicates, JabRef metadata
90
+ - **Lint** — validate required fields, DOI shape, key conflicts (CI-gate multiple files)
91
+ - **Normalize** — authors, DOIs, months, journals, `saveActions` pipeline
92
+ - **Format** — deterministic layout-only rewrites with explicit layout flags
93
+ - **Import** by DOI or arXiv identifier, with configurable key generation, or add a manual entry
94
+ - **Dedupe & merge** — detect and resolve duplicates, with conflict reporting
95
+ - **Bulk-edit fields** — set, rename, move, append, clear, or protect fields on matching references
96
+ - **Manage groups and keys** — list, rename, repair, generate from patterns
97
+ - **Convert** between BibTeX/BibLaTeX dialects; import/export CSL-JSON, RIS,
98
+ MODS, and EndNote; export CSV
99
+ - **Track citation usage** — find cited, unused, and missing keys in `.tex` or
100
+ `.aux` sources
101
+ - **Remove** entries with a single command
102
+
103
+ All through the standardized lifecycle: load → stage → preview/diff → commit.
104
+ JabRef v5.15 interoperability covers metadata preservation and classification,
105
+ offline-compatible `saveActions`, all four group types, save-order configuration,
106
+ and common key-pattern markers and modifiers. Unsupported formatter names are
107
+ reported rather than silently applied. Every modifying command supports
108
+ `--dry-run --diff` and atomic writes; backups are opt-in.
109
+
110
+ ### 2. Pinax — the corpus layer
111
+
112
+ Opt in by setting a `files-dir` in the library metadata. Now every citation key can carry materials:
113
+
114
+ - **arXiv download** — PDFs and source bundles, automatically fetched, verified, and extracted with provenance tracking (source hash, download timestamp)
115
+ - **Open-access published PDFs** — resolved from DOI via OpenAlex, CrossRef, and publisher-specific URL overrides; `.published.pdf` lands only for genuinely OA papers, never misidentified repository mirrors
116
+ - **`asset fetch`** — download configured materials for all entries or one key
117
+ - **`asset check`** — validate presence, detect orphans, verify checksums, optionally fix (`--fix`)
118
+ - **`ref remove`** — removes both the entry and its materials by default
119
+ - **Coordinated key edits** — renaming a key moves its materials
120
+ - **`corpus combine`/`corpus split`** — materials follow their entries
121
+
122
+ The `.bib` stays the source of truth; Pinax just keeps the shelf tidy.
123
+
124
+ ## Installation
125
+
126
+ ```bash
127
+ pip install pynakes
128
+ ```
129
+
130
+ For development (with test/lint tools):
131
+
132
+ ```bash
133
+ git clone https://github.com/maiani/pynakes.git
134
+ cd pynakes
135
+ pip install -e ".[dev]"
136
+ pre-commit install # enable ruff hooks on commit
137
+ ```
138
+
139
+ See the [Installation guide](docs/guides/installation.md) for shell completion and troubleshooting.
140
+
141
+ ## Quick start
142
+
143
+ ```bash
144
+ # Create a new library, then inspect and check for issues
145
+ pynakes init mylib.bib
146
+ pynakes inspect mylib.bib
147
+ pynakes lint mylib.bib --json
148
+
149
+ # Import a reference by DOI or arXiv id, or add one manually
150
+ pynakes ref import 10.5555/example mylib.bib
151
+ pynakes ref import arXiv:2301.00001 mylib.bib
152
+ pynakes ref add Manual2026 mylib.bib --field title="Manual Reference" --field year=2026
153
+ pynakes ref add # interactive
154
+ pynakes ref show Manual2026 mylib.bib
155
+ pynakes ref edit Manual2026 mylib.bib --field year=2027 --clear-field note
156
+ pynakes ref edit Manual2026 mylib.bib # interactive
157
+
158
+ # Preview the normalization pass before committing
159
+ pynakes normalize mylib.bib --dry-run --diff
160
+
161
+ # Remove entries by citation key (with pinax material cleanup)
162
+ pynakes ref remove mylib.bib DeprecatedKey2020
163
+
164
+ # Rename a citation key across the .bib file and .tex sources
165
+ pynakes keys rename mylib.bib OldKey2020 NewKey2020 paper.tex chapters/
166
+
167
+ # Fetch configured materials for one entry (Pinax mode)
168
+ pynakes asset fetch arXivKey2024 mylib.bib
169
+
170
+ # Search entries
171
+ pynakes search mylib.bib "neural network" --json
172
+
173
+ # Gate a build: fail if any .bib has errors
174
+ pynakes lint mylib.bib chapters/*.bib --strict
175
+
176
+ # Shell completion for cite keys (bash/zsh/fish)
177
+ eval "$(pynakes --show-completion bash)"
178
+ ```
179
+
180
+ When the current directory contains exactly one `.bib` file, its path may be omitted from commands that operate on a single library (for example, `pynakes normalize --dry-run`).
181
+
182
+ Commands still require an explicit path when there are multiple `.bib` files.
183
+
184
+ See the [Quick Start guide](docs/guides/quickstart.md) and [Usage guide](docs/guides/usage.md) for the full command surface.
185
+
186
+ ## Safety model
187
+
188
+ Every modifying command:
189
+
190
+ - Validates before writing (parse errors, conflicts, missing required fields)
191
+ - Supports `--dry-run` and `--diff` to preview changes
192
+ - Writes atomically and optionally creates a `.bak` backup
193
+ - Reports conflicts and exits `2` rather than guessing (e.g. duplicate DOI on import)
194
+ - Emits structured JSON (`--json`) for programmatic use
195
+
196
+ ## For scripted and LLM-assisted workflows
197
+
198
+ In an agentic workflow, the agent supplies judgment and pynakes supplies the
199
+ deterministic execution layer. The same interface is safe for ordinary scripts
200
+ and CI:
201
+
202
+ - `--json` returns machine-readable results with status, warnings, and errors
203
+ - `pynakes capabilities --json` describes supported operations for an agent
204
+ - Exit codes: `0` success, `1` error, `2` conflict (safe to retry with user input)
205
+
206
+ See the [LLM Integration guide](docs/guides/llm-integration.md) for the full JSON envelope and recommended workflows.
207
+
208
+ ## Status
209
+
210
+ v0.5.0 is the first public alpha. It includes the preservation-first
211
+ parser/writer, the documented maintenance command surface, broad JabRef v5.15
212
+ interoperability, machine-readable command discovery, and the optional Pinax
213
+ corpus layer with arXiv and published-PDF material workflows. CI tests Python
214
+ 3.11–3.13 and enforces a 90% coverage floor.
215
+
216
+ Until v1.0, pynakes does **not** guarantee backward compatibility for the Python
217
+ API, CLI syntax, or JSON envelopes. The project aims to keep automation
218
+ workflows predictable and documents breaking changes in the changelog, but
219
+ production users should pin exact `0.x` versions.
220
+
221
+ Parser conformance is verified against the TeX Live 2026 toolchain (BibTeX 0.99d,
222
+ BibLaTeX 3.21, Biber 2.21). The project roadmap is documented in
223
+ [DEVPLAN.md](DEVPLAN.md).
224
+
225
+ ## Documentation
226
+
227
+ - [Quick Start](docs/guides/quickstart.md)
228
+ - [Usage Guide](docs/guides/usage.md)
229
+ - [Installation](docs/guides/installation.md)
230
+ - [LLM Integration](docs/guides/llm-integration.md)
231
+ - [Git Workflows: pre-commit & CI](docs/guides/git-workflows.md)
232
+ - [Architecture](docs/guides/architecture.md)
233
+ - [Pinax — the corpus layer](docs/guides/pinax.md)
234
+ - [API Reference](docs/api/reference.md)
235
+
236
+ ## License
237
+
238
+ MIT — see [LICENSE](LICENSE).
239
+
240
+ ## Contributing
241
+
242
+ Contributions are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md).
@@ -0,0 +1,197 @@
1
+ # pynakes
2
+
3
+ <p align="center">
4
+ <img src="https://raw.githubusercontent.com/maiani/pynakes/main/docs/assets/logo.svg" alt="pynakes logo" width="128">
5
+ </p>
6
+
7
+ [![PyPI](https://img.shields.io/pypi/v/pynakes.svg)](https://pypi.org/project/pynakes/)
8
+ [![Python](https://img.shields.io/pypi/pyversions/pynakes.svg)](https://pypi.org/project/pynakes/)
9
+ [![CI](https://img.shields.io/github/actions/workflow/status/maiani/pynakes/ci.yml?branch=main)](https://github.com/maiani/pynakes/actions)
10
+ [![License](https://img.shields.io/github/license/maiani/pynakes)](https://github.com/maiani/pynakes/blob/main/LICENSE)
11
+
12
+ > [!WARNING]
13
+ > `pynakes` is alpha-quality software. Interfaces and behavior may change between releases.
14
+
15
+ `pynakes` is a deterministic bibliography maintenance engine built for reliable agentic workflows, CI, and human use. Agents decide what should change; pynakes inspects, validates, previews, and applies those changes safely and reproducibly.
16
+
17
+ The Python CLI makes small, explicit, reviewable changes to `.bib` files —
18
+ minimal diffs, dry-run previews, and atomic writes — and reports every change
19
+ as structured JSON.
20
+
21
+ Requires Python 3.11+ and is packaged as an OS-independent CLI and library.
22
+
23
+ Untouched entries write back byte-for-byte — no hidden reformatting, reordering, or re-quoting. The file stays diff-friendly in git and yours for decades.
24
+
25
+ Named after the *Pinakes*, Callimachus's catalog of the Library of Alexandria — antiquity's first bibliography.
26
+ A `.bib` file is the index card; a **pinax** is the card together with the shelf it points at.
27
+
28
+ ## Why pynakes
29
+
30
+ - **Reviewable by design.** Modifying commands support `--dry-run --diff` for review before writing. Writes are atomic and re-parse-validated; `--backup` optionally retains the previous file. Ambiguous cases — conflicting merges, duplicate DOIs — are reported with exit code `2` rather than guessed.
31
+ - **Round-trip fidelity.** An entry you don't touch is written back byte-for-byte. pynakes never normalizes whitespace, reorders fields, or re-quotes values behind your back — so diffs stay tiny and reviewable.
32
+ - **A deterministic execution layer for agents and CI.** Stable JSON output and exit codes, machine-readable `capabilities`, structured change plans, and `--strict` / pre-commit gates let an agent exercise judgment without directly rewriting bibliography text.
33
+ - **Deterministic and offline by default.** No hidden time, randomness, or ordering; network access is explicit (`ref import`, `asset fetch`, or `--online`) and confined to the few commands that need it.
34
+ - **Preservation-first interoperability.** Untouched BibTeX/BibLaTeX source and
35
+ JabRef metadata round-trip unchanged. `pynakes-meta` holds native settings;
36
+ JabRef metadata can be preserved, adopted, and projected explicitly.
37
+
38
+ ## Two workflows
39
+
40
+ ### 1. Bibliography engine
41
+ Load any `.bib` file the BibTeX/BibLaTeX toolchain produces,
42
+ then:
43
+
44
+ - **Inspect** — entry count, encoding, duplicates, JabRef metadata
45
+ - **Lint** — validate required fields, DOI shape, key conflicts (CI-gate multiple files)
46
+ - **Normalize** — authors, DOIs, months, journals, `saveActions` pipeline
47
+ - **Format** — deterministic layout-only rewrites with explicit layout flags
48
+ - **Import** by DOI or arXiv identifier, with configurable key generation, or add a manual entry
49
+ - **Dedupe & merge** — detect and resolve duplicates, with conflict reporting
50
+ - **Bulk-edit fields** — set, rename, move, append, clear, or protect fields on matching references
51
+ - **Manage groups and keys** — list, rename, repair, generate from patterns
52
+ - **Convert** between BibTeX/BibLaTeX dialects; import/export CSL-JSON, RIS,
53
+ MODS, and EndNote; export CSV
54
+ - **Track citation usage** — find cited, unused, and missing keys in `.tex` or
55
+ `.aux` sources
56
+ - **Remove** entries with a single command
57
+
58
+ All through the standardized lifecycle: load → stage → preview/diff → commit.
59
+ JabRef v5.15 interoperability covers metadata preservation and classification,
60
+ offline-compatible `saveActions`, all four group types, save-order configuration,
61
+ and common key-pattern markers and modifiers. Unsupported formatter names are
62
+ reported rather than silently applied. Every modifying command supports
63
+ `--dry-run --diff` and atomic writes; backups are opt-in.
64
+
65
+ ### 2. Pinax — the corpus layer
66
+
67
+ Opt in by setting a `files-dir` in the library metadata. Now every citation key can carry materials:
68
+
69
+ - **arXiv download** — PDFs and source bundles, automatically fetched, verified, and extracted with provenance tracking (source hash, download timestamp)
70
+ - **Open-access published PDFs** — resolved from DOI via OpenAlex, CrossRef, and publisher-specific URL overrides; `.published.pdf` lands only for genuinely OA papers, never misidentified repository mirrors
71
+ - **`asset fetch`** — download configured materials for all entries or one key
72
+ - **`asset check`** — validate presence, detect orphans, verify checksums, optionally fix (`--fix`)
73
+ - **`ref remove`** — removes both the entry and its materials by default
74
+ - **Coordinated key edits** — renaming a key moves its materials
75
+ - **`corpus combine`/`corpus split`** — materials follow their entries
76
+
77
+ The `.bib` stays the source of truth; Pinax just keeps the shelf tidy.
78
+
79
+ ## Installation
80
+
81
+ ```bash
82
+ pip install pynakes
83
+ ```
84
+
85
+ For development (with test/lint tools):
86
+
87
+ ```bash
88
+ git clone https://github.com/maiani/pynakes.git
89
+ cd pynakes
90
+ pip install -e ".[dev]"
91
+ pre-commit install # enable ruff hooks on commit
92
+ ```
93
+
94
+ See the [Installation guide](docs/guides/installation.md) for shell completion and troubleshooting.
95
+
96
+ ## Quick start
97
+
98
+ ```bash
99
+ # Create a new library, then inspect and check for issues
100
+ pynakes init mylib.bib
101
+ pynakes inspect mylib.bib
102
+ pynakes lint mylib.bib --json
103
+
104
+ # Import a reference by DOI or arXiv id, or add one manually
105
+ pynakes ref import 10.5555/example mylib.bib
106
+ pynakes ref import arXiv:2301.00001 mylib.bib
107
+ pynakes ref add Manual2026 mylib.bib --field title="Manual Reference" --field year=2026
108
+ pynakes ref add # interactive
109
+ pynakes ref show Manual2026 mylib.bib
110
+ pynakes ref edit Manual2026 mylib.bib --field year=2027 --clear-field note
111
+ pynakes ref edit Manual2026 mylib.bib # interactive
112
+
113
+ # Preview the normalization pass before committing
114
+ pynakes normalize mylib.bib --dry-run --diff
115
+
116
+ # Remove entries by citation key (with pinax material cleanup)
117
+ pynakes ref remove mylib.bib DeprecatedKey2020
118
+
119
+ # Rename a citation key across the .bib file and .tex sources
120
+ pynakes keys rename mylib.bib OldKey2020 NewKey2020 paper.tex chapters/
121
+
122
+ # Fetch configured materials for one entry (Pinax mode)
123
+ pynakes asset fetch arXivKey2024 mylib.bib
124
+
125
+ # Search entries
126
+ pynakes search mylib.bib "neural network" --json
127
+
128
+ # Gate a build: fail if any .bib has errors
129
+ pynakes lint mylib.bib chapters/*.bib --strict
130
+
131
+ # Shell completion for cite keys (bash/zsh/fish)
132
+ eval "$(pynakes --show-completion bash)"
133
+ ```
134
+
135
+ When the current directory contains exactly one `.bib` file, its path may be omitted from commands that operate on a single library (for example, `pynakes normalize --dry-run`).
136
+
137
+ Commands still require an explicit path when there are multiple `.bib` files.
138
+
139
+ See the [Quick Start guide](docs/guides/quickstart.md) and [Usage guide](docs/guides/usage.md) for the full command surface.
140
+
141
+ ## Safety model
142
+
143
+ Every modifying command:
144
+
145
+ - Validates before writing (parse errors, conflicts, missing required fields)
146
+ - Supports `--dry-run` and `--diff` to preview changes
147
+ - Writes atomically and optionally creates a `.bak` backup
148
+ - Reports conflicts and exits `2` rather than guessing (e.g. duplicate DOI on import)
149
+ - Emits structured JSON (`--json`) for programmatic use
150
+
151
+ ## For scripted and LLM-assisted workflows
152
+
153
+ In an agentic workflow, the agent supplies judgment and pynakes supplies the
154
+ deterministic execution layer. The same interface is safe for ordinary scripts
155
+ and CI:
156
+
157
+ - `--json` returns machine-readable results with status, warnings, and errors
158
+ - `pynakes capabilities --json` describes supported operations for an agent
159
+ - Exit codes: `0` success, `1` error, `2` conflict (safe to retry with user input)
160
+
161
+ See the [LLM Integration guide](docs/guides/llm-integration.md) for the full JSON envelope and recommended workflows.
162
+
163
+ ## Status
164
+
165
+ v0.5.0 is the first public alpha. It includes the preservation-first
166
+ parser/writer, the documented maintenance command surface, broad JabRef v5.15
167
+ interoperability, machine-readable command discovery, and the optional Pinax
168
+ corpus layer with arXiv and published-PDF material workflows. CI tests Python
169
+ 3.11–3.13 and enforces a 90% coverage floor.
170
+
171
+ Until v1.0, pynakes does **not** guarantee backward compatibility for the Python
172
+ API, CLI syntax, or JSON envelopes. The project aims to keep automation
173
+ workflows predictable and documents breaking changes in the changelog, but
174
+ production users should pin exact `0.x` versions.
175
+
176
+ Parser conformance is verified against the TeX Live 2026 toolchain (BibTeX 0.99d,
177
+ BibLaTeX 3.21, Biber 2.21). The project roadmap is documented in
178
+ [DEVPLAN.md](DEVPLAN.md).
179
+
180
+ ## Documentation
181
+
182
+ - [Quick Start](docs/guides/quickstart.md)
183
+ - [Usage Guide](docs/guides/usage.md)
184
+ - [Installation](docs/guides/installation.md)
185
+ - [LLM Integration](docs/guides/llm-integration.md)
186
+ - [Git Workflows: pre-commit & CI](docs/guides/git-workflows.md)
187
+ - [Architecture](docs/guides/architecture.md)
188
+ - [Pinax — the corpus layer](docs/guides/pinax.md)
189
+ - [API Reference](docs/api/reference.md)
190
+
191
+ ## License
192
+
193
+ MIT — see [LICENSE](LICENSE).
194
+
195
+ ## Contributing
196
+
197
+ Contributions are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md).
@@ -0,0 +1,98 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pynakes"
7
+ version = "0.5.1"
8
+ description = "Agent-friendly, round-trip-faithful BibTeX/BibLaTeX maintenance toolkit"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ requires-python = ">=3.11"
13
+ authors = [
14
+ {name = "Andrea Maiani", email = "dev@andreamaiani.com"}
15
+ ]
16
+ keywords = [
17
+ "bibtex",
18
+ "biblatex",
19
+ "bibliography",
20
+ "citations",
21
+ "references",
22
+ "jabref",
23
+ "latex",
24
+ "doi",
25
+ "reference-management",
26
+ "cli",
27
+ "agent",
28
+ ]
29
+ classifiers = [
30
+ "Development Status :: 3 - Alpha",
31
+ "Environment :: Console",
32
+ "Intended Audience :: Science/Research",
33
+ "Intended Audience :: Developers",
34
+ "Operating System :: OS Independent",
35
+ "Programming Language :: Python :: 3",
36
+ "Programming Language :: Python :: 3.11",
37
+ "Programming Language :: Python :: 3.12",
38
+ "Programming Language :: Python :: 3.13",
39
+ "Topic :: Scientific/Engineering",
40
+ "Topic :: Text Processing :: Markup :: LaTeX",
41
+ "Topic :: Software Development :: Libraries :: Python Modules",
42
+ "Typing :: Typed",
43
+ ]
44
+ dependencies = [
45
+ "click>=8.1.0",
46
+ "httpx>=0.27.0",
47
+ "typer>=0.12.0",
48
+ "rich>=13.7.0",
49
+ ]
50
+
51
+ [project.optional-dependencies]
52
+ dev = [
53
+ "pre-commit>=3.6.0",
54
+ "pytest>=7.4.0",
55
+ "pytest-cov>=4.1.0",
56
+ "hypothesis>=6.100.0",
57
+ "ruff>=0.1.0",
58
+ "build>=1.2.0",
59
+ "twine>=5.0.0",
60
+ ]
61
+ docs = [
62
+ "mkdocs>=1.6",
63
+ "mkdocs-material>=9.5",
64
+ "mkdocstrings[python]>=0.26",
65
+ ]
66
+
67
+ [project.urls]
68
+ Homepage = "https://github.com/maiani/pynakes"
69
+ Repository = "https://github.com/maiani/pynakes"
70
+ Issues = "https://github.com/maiani/pynakes/issues"
71
+ Changelog = "https://github.com/maiani/pynakes/blob/main/CHANGELOG.md"
72
+
73
+ [project.scripts]
74
+ pynakes = "pynakes.cli:app"
75
+
76
+ [tool.setuptools.packages.find]
77
+ where = ["src"]
78
+
79
+ [tool.setuptools.package-data]
80
+ pynakes = ["py.typed", "providers/pdf_overrides.json"]
81
+
82
+ [tool.pytest.ini_options]
83
+ testpaths = ["tests"]
84
+ addopts = "-v --tb=short"
85
+ markers = [
86
+ "agent_eval: opt-in real-agent beta-test harness",
87
+ ]
88
+
89
+ [tool.ruff]
90
+ target-version = "py311"
91
+ line-length = 100
92
+
93
+ [tool.ruff.lint]
94
+ select = ["E", "F", "W", "I"]
95
+ ignore = ["E501"]
96
+
97
+ [tool.coverage.run]
98
+ source = ["src/pynakes"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,8 @@
1
+ """pynakes: Agent-friendly BibTeX library management."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ __version__ = version("pynakes")
7
+ except PackageNotFoundError: # running from a source tree that isn't installed
8
+ __version__ = "0.0.0+unknown"
@@ -0,0 +1,6 @@
1
+ """Entry point for running pynakes as a module."""
2
+
3
+ from pynakes.cli import app
4
+
5
+ if __name__ == "__main__":
6
+ app()