crossref-local 0.4.0__py3-none-any.whl → 0.5.1__py3-none-any.whl

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 (50) hide show
  1. crossref_local/__init__.py +24 -10
  2. crossref_local/_aio/__init__.py +30 -0
  3. crossref_local/_aio/_impl.py +238 -0
  4. crossref_local/_cache/__init__.py +15 -0
  5. crossref_local/{cache_export.py → _cache/export.py} +27 -10
  6. crossref_local/_cache/utils.py +93 -0
  7. crossref_local/_cli/__init__.py +9 -0
  8. crossref_local/_cli/cli.py +389 -0
  9. crossref_local/_cli/mcp.py +351 -0
  10. crossref_local/_cli/mcp_server.py +457 -0
  11. crossref_local/_cli/search.py +199 -0
  12. crossref_local/_core/__init__.py +62 -0
  13. crossref_local/{api.py → _core/api.py} +26 -5
  14. crossref_local/{citations.py → _core/citations.py} +55 -26
  15. crossref_local/{config.py → _core/config.py} +40 -22
  16. crossref_local/{db.py → _core/db.py} +32 -26
  17. crossref_local/_core/export.py +344 -0
  18. crossref_local/{fts.py → _core/fts.py} +37 -14
  19. crossref_local/{models.py → _core/models.py} +120 -6
  20. crossref_local/_remote/__init__.py +56 -0
  21. crossref_local/_remote/base.py +378 -0
  22. crossref_local/_remote/collections.py +175 -0
  23. crossref_local/_server/__init__.py +140 -0
  24. crossref_local/_server/middleware.py +25 -0
  25. crossref_local/_server/models.py +143 -0
  26. crossref_local/_server/routes_citations.py +98 -0
  27. crossref_local/_server/routes_collections.py +282 -0
  28. crossref_local/_server/routes_compat.py +102 -0
  29. crossref_local/_server/routes_works.py +178 -0
  30. crossref_local/_server/server.py +19 -0
  31. crossref_local/aio.py +30 -206
  32. crossref_local/cache.py +100 -100
  33. crossref_local/cli.py +5 -515
  34. crossref_local/jobs.py +169 -0
  35. crossref_local/mcp_server.py +5 -410
  36. crossref_local/remote.py +5 -266
  37. crossref_local/server.py +5 -349
  38. {crossref_local-0.4.0.dist-info → crossref_local-0.5.1.dist-info}/METADATA +36 -11
  39. crossref_local-0.5.1.dist-info/RECORD +49 -0
  40. {crossref_local-0.4.0.dist-info → crossref_local-0.5.1.dist-info}/entry_points.txt +1 -1
  41. crossref_local/cli_mcp.py +0 -275
  42. crossref_local-0.4.0.dist-info/RECORD +0 -27
  43. /crossref_local/{cache_viz.py → _cache/viz.py} +0 -0
  44. /crossref_local/{cli_cache.py → _cli/cache.py} +0 -0
  45. /crossref_local/{cli_completion.py → _cli/completion.py} +0 -0
  46. /crossref_local/{cli_main.py → _cli/main.py} +0 -0
  47. /crossref_local/{impact_factor → _impact_factor}/__init__.py +0 -0
  48. /crossref_local/{impact_factor → _impact_factor}/calculator.py +0 -0
  49. /crossref_local/{impact_factor → _impact_factor}/journal_lookup.py +0 -0
  50. {crossref_local-0.4.0.dist-info → crossref_local-0.5.1.dist-info}/WHEEL +0 -0
crossref_local/server.py CHANGED
@@ -1,352 +1,8 @@
1
- """FastAPI server for CrossRef Local with FTS5 search.
1
+ #!/usr/bin/env python3
2
+ """Backward compatibility: re-export from _server."""
2
3
 
3
- This server provides proper full-text search using FTS5 index,
4
- unlike the Django API which only scans a limited subset.
4
+ from ._server import app, run_server, DEFAULT_PORT, DEFAULT_HOST
5
5
 
6
- Usage:
7
- crossref-local api # Run on default port 8333
8
- crossref-local api --port 8080 # Custom port
6
+ __all__ = ["app", "run_server", "DEFAULT_PORT", "DEFAULT_HOST"]
9
7
 
10
- # Or directly:
11
- uvicorn crossref_local.server:app --host 0.0.0.0 --port 8333
12
- """
13
-
14
- import time
15
- from typing import Optional, List
16
-
17
- from fastapi import FastAPI, Query, HTTPException
18
- from fastapi.middleware.cors import CORSMiddleware
19
- from pydantic import BaseModel
20
-
21
- from . import fts, __version__
22
- from .db import get_db
23
- from .models import Work
24
-
25
- app = FastAPI(
26
- title="CrossRef Local API",
27
- description="Fast full-text search across 167M+ scholarly works",
28
- version=__version__,
29
- )
30
-
31
- # CORS middleware
32
- app.add_middleware(
33
- CORSMiddleware,
34
- allow_origins=["*"],
35
- allow_methods=["*"],
36
- allow_headers=["*"],
37
- )
38
-
39
-
40
- class WorkResponse(BaseModel):
41
- doi: str
42
- title: Optional[str] = None
43
- authors: List[str] = []
44
- year: Optional[int] = None
45
- journal: Optional[str] = None
46
- issn: Optional[str] = None
47
- volume: Optional[str] = None
48
- issue: Optional[str] = None
49
- page: Optional[str] = None
50
- abstract: Optional[str] = None
51
- citation_count: Optional[int] = None
52
-
53
-
54
- class SearchResponse(BaseModel):
55
- query: str
56
- total: int
57
- returned: int
58
- elapsed_ms: float
59
- results: List[WorkResponse]
60
-
61
-
62
- class InfoResponse(BaseModel):
63
- name: str = "CrossRef Local API"
64
- version: str = __version__
65
- status: str = "running"
66
- mode: str = "local"
67
- total_papers: int
68
- fts_indexed: int
69
- citations: int
70
- database_path: str
71
-
72
-
73
- @app.get("/")
74
- def root():
75
- """API root with endpoint information."""
76
- return {
77
- "name": "CrossRef Local API",
78
- "version": __version__,
79
- "status": "running",
80
- "endpoints": {
81
- "health": "/health",
82
- "info": "/info",
83
- "search": "/works?q=<query>",
84
- "get_by_doi": "/works/{doi}",
85
- "batch": "/works/batch",
86
- },
87
- }
88
-
89
-
90
- @app.get("/health")
91
- def health():
92
- """Health check endpoint."""
93
- db = get_db()
94
- return {
95
- "status": "healthy",
96
- "database_connected": db is not None,
97
- "database_path": str(db.db_path) if db else None,
98
- }
99
-
100
-
101
- @app.get("/info", response_model=InfoResponse)
102
- def info():
103
- """Get database statistics."""
104
- db = get_db()
105
-
106
- row = db.fetchone("SELECT COUNT(*) as count FROM works")
107
- work_count = row["count"] if row else 0
108
-
109
- try:
110
- row = db.fetchone("SELECT COUNT(*) as count FROM works_fts")
111
- fts_count = row["count"] if row else 0
112
- except Exception:
113
- fts_count = 0
114
-
115
- try:
116
- row = db.fetchone("SELECT COUNT(*) as count FROM citations")
117
- citation_count = row["count"] if row else 0
118
- except Exception:
119
- citation_count = 0
120
-
121
- return InfoResponse(
122
- total_papers=work_count,
123
- fts_indexed=fts_count,
124
- citations=citation_count,
125
- database_path=str(db.db_path),
126
- )
127
-
128
-
129
- @app.get("/works", response_model=SearchResponse)
130
- def search_works(
131
- q: str = Query(..., description="Search query (FTS5 syntax supported)"),
132
- limit: int = Query(10, ge=1, le=100, description="Max results"),
133
- offset: int = Query(0, ge=0, description="Skip first N results"),
134
- ):
135
- """
136
- Full-text search across works.
137
-
138
- Uses FTS5 index for fast searching across titles, abstracts, and authors.
139
- Supports FTS5 query syntax like AND, OR, NOT, "exact phrases".
140
-
141
- Examples:
142
- /works?q=machine learning
143
- /works?q="neural network" AND hippocampus
144
- /works?q=CRISPR&limit=20
145
- """
146
- start = time.perf_counter()
147
-
148
- try:
149
- results = fts.search(q, limit=limit, offset=offset)
150
- except Exception as e:
151
- raise HTTPException(status_code=400, detail=f"Search error: {e}")
152
-
153
- elapsed_ms = (time.perf_counter() - start) * 1000
154
-
155
- return SearchResponse(
156
- query=q,
157
- total=results.total,
158
- returned=len(results.works),
159
- elapsed_ms=round(elapsed_ms, 2),
160
- results=[
161
- WorkResponse(
162
- doi=w.doi,
163
- title=w.title,
164
- authors=w.authors,
165
- year=w.year,
166
- journal=w.journal,
167
- issn=w.issn,
168
- volume=w.volume,
169
- issue=w.issue,
170
- page=w.page,
171
- abstract=w.abstract,
172
- citation_count=w.citation_count,
173
- )
174
- for w in results.works
175
- ],
176
- )
177
-
178
-
179
- @app.get("/works/{doi:path}", response_model=Optional[WorkResponse])
180
- def get_work(doi: str):
181
- """
182
- Get work metadata by DOI.
183
-
184
- Examples:
185
- /works/10.1038/nature12373
186
- /works/10.1016/j.cell.2020.01.001
187
- """
188
- db = get_db()
189
- metadata = db.get_metadata(doi)
190
-
191
- if metadata is None:
192
- raise HTTPException(status_code=404, detail=f"DOI not found: {doi}")
193
-
194
- work = Work.from_metadata(doi, metadata)
195
-
196
- return WorkResponse(
197
- doi=work.doi,
198
- title=work.title,
199
- authors=work.authors,
200
- year=work.year,
201
- journal=work.journal,
202
- issn=work.issn,
203
- volume=work.volume,
204
- issue=work.issue,
205
- page=work.page,
206
- abstract=work.abstract,
207
- citation_count=work.citation_count,
208
- )
209
-
210
-
211
- class BatchRequest(BaseModel):
212
- dois: List[str]
213
-
214
-
215
- class BatchResponse(BaseModel):
216
- requested: int
217
- found: int
218
- results: List[WorkResponse]
219
-
220
-
221
- @app.post("/works/batch", response_model=BatchResponse)
222
- def get_works_batch(request: BatchRequest):
223
- """
224
- Get multiple works by DOI.
225
-
226
- Request body: {"dois": ["10.1038/...", "10.1016/..."]}
227
- """
228
- db = get_db()
229
- results = []
230
-
231
- for doi in request.dois:
232
- metadata = db.get_metadata(doi)
233
- if metadata:
234
- work = Work.from_metadata(doi, metadata)
235
- results.append(
236
- WorkResponse(
237
- doi=work.doi,
238
- title=work.title,
239
- authors=work.authors,
240
- year=work.year,
241
- journal=work.journal,
242
- abstract=work.abstract,
243
- citation_count=work.citation_count,
244
- )
245
- )
246
-
247
- return BatchResponse(
248
- requested=len(request.dois),
249
- found=len(results),
250
- results=results,
251
- )
252
-
253
-
254
- # For backwards compatibility with existing API endpoints
255
- @app.get("/api/search/")
256
- def api_search_compat(
257
- title: Optional[str] = None,
258
- q: Optional[str] = None,
259
- doi: Optional[str] = None,
260
- limit: int = 10,
261
- ):
262
- """Backwards-compatible search endpoint."""
263
- query = title or q
264
-
265
- if doi:
266
- # DOI lookup
267
- try:
268
- work = get_work(doi)
269
- return {
270
- "query": {"doi": doi},
271
- "results": [work.model_dump()],
272
- "total": 1,
273
- "returned": 1,
274
- }
275
- except HTTPException:
276
- return {"query": {"doi": doi}, "results": [], "total": 0, "returned": 0}
277
-
278
- if not query:
279
- raise HTTPException(
280
- status_code=400, detail="Specify q, title, or doi parameter"
281
- )
282
-
283
- # Call fts.search directly (not the endpoint function)
284
- results = fts.search(query, limit=limit, offset=0)
285
- return {
286
- "query": {
287
- "title": query,
288
- "doi": None,
289
- "year": None,
290
- "authors": None,
291
- "limit": limit,
292
- },
293
- "results": [
294
- WorkResponse(
295
- doi=w.doi,
296
- title=w.title,
297
- authors=w.authors,
298
- year=w.year,
299
- journal=w.journal,
300
- issn=w.issn,
301
- volume=w.volume,
302
- issue=w.issue,
303
- page=w.page,
304
- abstract=w.abstract,
305
- citation_count=w.citation_count,
306
- ).model_dump()
307
- for w in results.works
308
- ],
309
- "total": results.total,
310
- "returned": len(results.works),
311
- }
312
-
313
-
314
- @app.get("/api/stats/")
315
- def api_stats_compat():
316
- """Backwards-compatible stats endpoint."""
317
- db = get_db()
318
-
319
- row = db.fetchone("SELECT COUNT(*) as count FROM works")
320
- work_count = row["count"] if row else 0
321
-
322
- # Get table names
323
- tables = []
324
- for row in db.fetchall("SELECT name FROM sqlite_master WHERE type='table'"):
325
- tables.append(row["name"])
326
-
327
- # Get index names
328
- indices = []
329
- for row in db.fetchall("SELECT name FROM sqlite_master WHERE type='index'"):
330
- if row["name"]:
331
- indices.append(row["name"])
332
-
333
- return {
334
- "total_papers": work_count,
335
- "database_size_mb": None,
336
- "year_range": None,
337
- "total_journals": 0,
338
- "total_citations": None,
339
- "tables": tables,
340
- "indices": indices,
341
- }
342
-
343
-
344
- def run_server(host: str = "0.0.0.0", port: int = 8333):
345
- """Run the FastAPI server."""
346
- import uvicorn
347
-
348
- uvicorn.run(app, host=host, port=port)
349
-
350
-
351
- if __name__ == "__main__":
352
- run_server()
8
+ # EOF
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: crossref-local
3
- Version: 0.4.0
3
+ Version: 0.5.1
4
4
  Summary: Local CrossRef database with 167M+ works and full-text search
5
5
  Project-URL: Homepage, https://github.com/ywatanabe1989/crossref_local
6
6
  Project-URL: Repository, https://github.com/ywatanabe1989/crossref_local
@@ -18,6 +18,7 @@ Classifier: Topic :: Database
18
18
  Classifier: Topic :: Scientific/Engineering
19
19
  Requires-Python: >=3.10
20
20
  Requires-Dist: click>=8.0
21
+ Requires-Dist: rich>=13.0
21
22
  Provides-Extra: all
22
23
  Requires-Dist: fastapi>=0.100; extra == 'all'
23
24
  Requires-Dist: fastmcp>=0.4; extra == 'all'
@@ -53,7 +54,10 @@ Description-Content-Type: text/markdown
53
54
 
54
55
  Local CrossRef database with 167M+ scholarly works, full-text search, and impact factor calculation.
55
56
 
57
+ [![PyPI version](https://badge.fury.io/py/crossref-local.svg)](https://badge.fury.io/py/crossref-local)
58
+ [![Documentation](https://readthedocs.org/projects/crossref-local/badge/?version=latest)](https://crossref-local.readthedocs.io/en/latest/)
56
59
  [![Tests](https://github.com/ywatanabe1989/crossref-local/actions/workflows/test.yml/badge.svg)](https://github.com/ywatanabe1989/crossref-local/actions/workflows/test.yml)
60
+ [![Coverage](https://codecov.io/gh/ywatanabe1989/crossref-local/branch/main/graph/badge.svg)](https://codecov.io/gh/ywatanabe1989/crossref-local)
57
61
  [![Python](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
58
62
  [![License](https://img.shields.io/badge/license-AGPL--3.0-blue.svg)](LICENSE)
59
63
 
@@ -181,34 +185,46 @@ Found 4 matches in 128.4ms
181
185
 
182
186
  Start the FastAPI server:
183
187
  ```bash
184
- crossref-local run-server-http --host 0.0.0.0 --port 8333
188
+ crossref-local relay --host 0.0.0.0 --port 31291
185
189
  ```
186
190
 
187
191
  Endpoints:
188
192
  ```bash
189
193
  # Search works (FTS5)
190
- curl "http://localhost:8333/works?q=CRISPR&limit=10"
194
+ curl "http://localhost:31291/works?q=CRISPR&limit=10"
191
195
 
192
196
  # Get by DOI
193
- curl "http://localhost:8333/works/10.1038/nature12373"
197
+ curl "http://localhost:31291/works/10.1038/nature12373"
194
198
 
195
199
  # Batch DOI lookup
196
- curl -X POST "http://localhost:8333/works/batch" \
200
+ curl -X POST "http://localhost:31291/works/batch" \
197
201
  -H "Content-Type: application/json" \
198
202
  -d '{"dois": ["10.1038/nature12373", "10.1126/science.aax0758"]}'
199
203
 
204
+ # Citation endpoints
205
+ curl "http://localhost:31291/citations/10.1038/nature12373/citing"
206
+ curl "http://localhost:31291/citations/10.1038/nature12373/cited"
207
+ curl "http://localhost:31291/citations/10.1038/nature12373/count"
208
+
209
+ # Collection endpoints
210
+ curl "http://localhost:31291/collections"
211
+ curl -X POST "http://localhost:31291/collections" \
212
+ -H "Content-Type: application/json" \
213
+ -d '{"name": "my_papers", "query": "CRISPR", "limit": 100}'
214
+ curl "http://localhost:31291/collections/my_papers/download?format=bibtex"
215
+
200
216
  # Database info
201
- curl "http://localhost:8333/info"
217
+ curl "http://localhost:31291/info"
202
218
  ```
203
219
 
204
220
  HTTP mode (connect to running server):
205
221
  ```bash
206
222
  # On local machine (if server is remote)
207
- ssh -L 8333:127.0.0.1:8333 your-server
223
+ ssh -L 31291:127.0.0.1:31291 your-server
208
224
 
209
225
  # Python client
210
226
  from crossref_local import configure_http
211
- configure_http("http://localhost:8333")
227
+ configure_http("http://localhost:31291")
212
228
 
213
229
  # Or via CLI
214
230
  crossref-local --http search "CRISPR"
@@ -221,7 +237,7 @@ crossref-local --http search "CRISPR"
221
237
 
222
238
  Run as MCP (Model Context Protocol) server:
223
239
  ```bash
224
- crossref-local run-server-mcp
240
+ crossref-local mcp start
225
241
  ```
226
242
 
227
243
  Local MCP client configuration:
@@ -230,7 +246,7 @@ Local MCP client configuration:
230
246
  "mcpServers": {
231
247
  "crossref-local": {
232
248
  "command": "crossref-local",
233
- "args": ["run-server-mcp"],
249
+ "args": ["mcp", "start"],
234
250
  "env": {
235
251
  "CROSSREF_LOCAL_DB": "/path/to/crossref.db"
236
252
  }
@@ -242,7 +258,7 @@ Local MCP client configuration:
242
258
  Remote MCP via HTTP (recommended):
243
259
  ```bash
244
260
  # On server: start persistent MCP server
245
- crossref-local run-server-mcp -t http --host 0.0.0.0 --port 8082
261
+ crossref-local mcp start -t http --host 0.0.0.0 --port 8082
246
262
  ```
247
263
  ```json
248
264
  {
@@ -254,12 +270,21 @@ crossref-local run-server-mcp -t http --host 0.0.0.0 --port 8082
254
270
  }
255
271
  ```
256
272
 
273
+ Diagnose setup:
274
+ ```bash
275
+ crossref-local mcp doctor # Check dependencies and database
276
+ crossref-local mcp list-tools # Show available MCP tools
277
+ crossref-local mcp installation # Show client config examples
278
+ ```
279
+
257
280
  See [docs/remote-deployment.md](docs/remote-deployment.md) for systemd and Docker setup.
258
281
 
259
282
  Available tools:
260
283
  - `search` - Full-text search across 167M+ papers
261
284
  - `search_by_doi` - Get paper by DOI
285
+ - `enrich_dois` - Add citation counts and references to DOIs
262
286
  - `status` - Database statistics
287
+ - `cache_*` - Paper collection management
263
288
 
264
289
  </details>
265
290
 
@@ -0,0 +1,49 @@
1
+ crossref_local/__init__.py,sha256=j2HyOmjYFUjiywoLdIgYVqgXuGx9eWjcy4x7VBA4vf0,3792
2
+ crossref_local/__main__.py,sha256=N1c1ESGgJkAwsSWXANUgmzxC1OJEIqw-cl9m4pmNP7s,110
3
+ crossref_local/aio.py,sha256=_vDHFoE2n6GqO3j6pGeCaJVVe5I5L3M0tuMlW8IgGx8,1255
4
+ crossref_local/cache.py,sha256=yQpnRRBsHx2pluIPPfJlrakeuCC8TysKXrfA3ohdxSg,13321
5
+ crossref_local/cli.py,sha256=2pWJK_vnO1IIwUrMB3K8KW94ZWfkEF7TLSxMsaR-LrI,137
6
+ crossref_local/jobs.py,sha256=1tyaFRjyQGVi8xqk5nYga7maIFq2qYTroBGwAeN5DFc,4835
7
+ crossref_local/mcp_server.py,sha256=YmUd3xKqSDnW6sYDC7KRpZ7sSTnbTnxxGw9s_5lqIt8,185
8
+ crossref_local/remote.py,sha256=PmvUq87mC76sM9BL9RczOaXtvwHoqMc_dN5PJCYT18M,239
9
+ crossref_local/server.py,sha256=SKoQ-cOoZjdXm24Sv1CFu3F8UclbD6QstfQb-7l2xtA,215
10
+ crossref_local/_aio/__init__.py,sha256=z5_RNd9PTQUslI5LWOCn0711s8cIRX7N-Y-S-JyutAU,371
11
+ crossref_local/_aio/_impl.py,sha256=uG5r0d92EcPDqB52w4Wjb7kf1GmjHrLEELHINmNXnJc,5626
12
+ crossref_local/_cache/__init__.py,sha256=QSSTVWB_zNU3JQRdnBCzHuIl93WM3udgO2NkRlgm9M0,272
13
+ crossref_local/_cache/export.py,sha256=ZyZS1vWZE9ui9weywejnBFI2FzNV1r1Q_3XXPiFIj3M,3028
14
+ crossref_local/_cache/utils.py,sha256=D3sg7MoXZs4aC2OvagVgmOVMZjm1Xqy1ulGbs0_F914,2502
15
+ crossref_local/_cache/viz.py,sha256=0VAHK-i1xR5StEYrJ_IAs0HSk0MQNVFi4EjPtlZEwTY,8498
16
+ crossref_local/_cli/__init__.py,sha256=NS07Eo93dRAuO5vxGwGwS0ctvZxMMkF_py09bzIk3Hk,175
17
+ crossref_local/_cli/cache.py,sha256=8QykJ7-H313x7oKRw5fJ9Coay27Y8uBWGx5JR1VQ1_Y,6220
18
+ crossref_local/_cli/cli.py,sha256=TFhj7BjSHxs3rWyOifpi39z6-YmHIHt5Jz-cZNtVtEg,12425
19
+ crossref_local/_cli/completion.py,sha256=yzqwMbGbbqXJQHdztuaqji-AdFTd8zDDhHeNGdw4NpU,7341
20
+ crossref_local/_cli/main.py,sha256=NgCGB5-ThofTRPoxxwZpsfCQTo5WIw8oP7zBSKhEiDQ,444
21
+ crossref_local/_cli/mcp.py,sha256=yjUoq3hvu2OofYf_D29_NXoTvkhxz5QbWAuWv6U-H8U,11427
22
+ crossref_local/_cli/mcp_server.py,sha256=mplCUBph9WM_Eq7b-MXgT-vvUH9hf4h_eeHUHTQ7f5c,12526
23
+ crossref_local/_cli/search.py,sha256=bqsWn4nCCW_Glgb7dSXsntP4P5nHQJJCSgehvnV9Bbc,6327
24
+ crossref_local/_core/__init__.py,sha256=xfun82MI9-SuaBqX0zB5liiLmj3AkY5TlksVYY6IaOg,1034
25
+ crossref_local/_core/api.py,sha256=fYkr2VkiAQur3fdysIXP57qk0njWBf9ngg6dt5t63Do,7880
26
+ crossref_local/_core/citations.py,sha256=4ERCTVkZu6kesZRazaWcTpUUeYz0rs97NwFZAYcLdKA,12765
27
+ crossref_local/_core/config.py,sha256=T-2EX4EDoCLaOAmViBKGQuNvoJP8x7cvZOvmAcNrQms,5475
28
+ crossref_local/_core/db.py,sha256=KLlcdNWLnKSexc0cuRZGsBLmJUnXeEaD_CBcXhbPXLg,3832
29
+ crossref_local/_core/export.py,sha256=uM_Qb9WXmn7v4tDOQjBlhJNQY0Y0WbefHxNLrRhe5tI,8899
30
+ crossref_local/_core/fts.py,sha256=Jsj3Cm9pihICk7Rvr9vIZbUsoxKzL0OligFFQxtQDVA,5030
31
+ crossref_local/_core/models.py,sha256=tbo0hIViKM9eJQXzp2APFhRvYd1UusUdJHugpPyNAEo,8697
32
+ crossref_local/_impact_factor/__init__.py,sha256=pcgVCPogBisANYE5Vp2PHVGPgxoMsSXr-6utqVE97-4,559
33
+ crossref_local/_impact_factor/calculator.py,sha256=eZ13URAZzPdRyAQpS8zXe_T33e2lm_gQhtoJCXbfIGM,15977
34
+ crossref_local/_impact_factor/journal_lookup.py,sha256=Ztx6ZeWxfmPvA3KfcW5h_yz01XPstIdk91j3nu2Q-qw,8846
35
+ crossref_local/_remote/__init__.py,sha256=bTA_bed9XsA0HDArm1o0WagsasxN0atzXdRI6I-r7Pk,1412
36
+ crossref_local/_remote/base.py,sha256=VlQCPdhRcUynB1IAnJAyfXhxzfsLa0tOSSINAH8lMuI,12160
37
+ crossref_local/_remote/collections.py,sha256=840HkakS_AtZbmRMYP-GpUX4S5oJ9jaJXzjivQgyozU,5022
38
+ crossref_local/_server/__init__.py,sha256=4fTk1r__5uWhC6XABVDV7PTPdglFIV32hr9KZ5WbKc8,3869
39
+ crossref_local/_server/middleware.py,sha256=6tdrfFCOA06i0HIDtrHZthY8DXbKy8rMEWHf-3Ve8-Y,926
40
+ crossref_local/_server/models.py,sha256=8ylDo3UhNoRGjXg5bmiUvB-BWy_8nahD-PSQSEK_8wk,2965
41
+ crossref_local/_server/routes_citations.py,sha256=DUpxl4FdZS7NheM93vCD6ntP5I1Hgw_D8mkGBEMBV2Y,2742
42
+ crossref_local/_server/routes_collections.py,sha256=NaWNTMcswmh87HTZYn5QpzxwtA_c18N3cqy6CrqCBNo,8135
43
+ crossref_local/_server/routes_compat.py,sha256=kSF4_DKg7lcp67Xii95A1pKDBc3kGcqGD-IPaK6TJA8,2726
44
+ crossref_local/_server/routes_works.py,sha256=YtDKh-xxTCvbDf8K7kwxWszIYjYz-PZzqJH8q13CoFE,5080
45
+ crossref_local/_server/server.py,sha256=D3SSXQ_cBwMqGd9tLwKK84JBDoisvQwVUuu11yr5zhE,579
46
+ crossref_local-0.5.1.dist-info/METADATA,sha256=RXoY2iFOTFVQcDQq0vPIwBpqz1rYPIiA4Uz-kj0-YZM,10748
47
+ crossref_local-0.5.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
48
+ crossref_local-0.5.1.dist-info/entry_points.txt,sha256=BZbDvHLHzlKzFc-dqLAFwPrWGmGq5yFuD3vslzbmRnk,111
49
+ crossref_local-0.5.1.dist-info/RECORD,,
@@ -1,3 +1,3 @@
1
1
  [console_scripts]
2
- crossref-local = crossref_local.cli_main:main
2
+ crossref-local = crossref_local.cli:main
3
3
  crossref-local-mcp = crossref_local.mcp_server:main