scholarcheck 0.1.0__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Guo Cheng
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,183 @@
1
+ Metadata-Version: 2.4
2
+ Name: scholarcheck
3
+ Version: 0.1.0
4
+ Summary: Verify citations against real metadata - stop hallucinated references. Zero dependencies.
5
+ Author: Guo Cheng
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/GuoCheng24/scholarcheck
8
+ Project-URL: Issues, https://github.com/GuoCheng24/scholarcheck/issues
9
+ Keywords: citations,bibtex,openalex,crossref,arxiv,literature-review,hallucination,research-tools,doi
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Scientific/Engineering
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Dynamic: license-file
20
+
21
+ # scholarcheck
22
+
23
+ [![test](https://github.com/GuoCheng24/scholarcheck/actions/workflows/test.yml/badge.svg)](https://github.com/GuoCheng24/scholarcheck/actions/workflows/test.yml) [![python](https://img.shields.io/badge/python-3.9%2B-blue)](https://www.python.org/) [![license](https://img.shields.io/badge/license-MIT-green)](LICENSE)
24
+
25
+ **Stop hallucinated citations.** Verify any reference against real metadata — from the command line, with zero dependencies.
26
+
27
+ <p align="center">
28
+ <img src="docs/three-states.png" width="100%">
29
+ </p>
30
+
31
+ <sub>The figure above is generated by <a href="docs/three-states_figure.py">docs/three-states_figure.py</a> — <code>pip install git+https://github.com/GuoCheng24/sciglyph</code> and run it to reproduce <code>docs/three-states.png</code> byte for byte.</sub>
32
+
33
+
34
+ Language models invent plausible-looking papers: right-sounding title, plausible authors, a DOI that resolves to nothing. `scholarcheck` answers one question honestly — **does this paper actually exist?** — by querying OpenAlex, Semantic Scholar, Crossref and arXiv directly.
35
+
36
+ ```console
37
+ $ scholarcheck verify "Deep Residual Learning for Image Recognition"
38
+ MATCH (high confidence) [query term coverage = 100%]
39
+ Deep Residual Learning for Image Recognition (2016, conference-paper; cited=226875) doi:10.1109/cvpr.2016.90
40
+ Kaiming He, Xiangyu Zhang, Shaoqing Ren et al.
41
+
42
+ $ scholarcheck verify "Quantum Topological Radiomics for Zebra Diagnosis in Martian Cohorts"
43
+ NOT FOUND in any of the four sources -> this citation is very likely hallucinated
44
+ ```
45
+
46
+ ## Why not just ask an AI assistant?
47
+
48
+ Because an assistant answers from memory, and memory is exactly what fails here. Three design choices make this different:
49
+
50
+ **1. It says "I could not check" instead of "it is fake."**
51
+ A verifier that reports a network outage as *hallucinated* is worse than no verifier. `scholarcheck` tracks every failed request and distinguishes the two:
52
+
53
+ ```console
54
+ $ scholarcheck verify "Attention Is All You Need" # with the network down
55
+ INCONCLUSIVE - could not query the sources, so nothing can be said about: Attention Is All You Need
56
+ Could not reach: api.openalex.org: curl: (7) Connection refused
57
+ (no proxy set; if your network needs one, set SCHOLARCHECK_PROXY)
58
+ ```
59
+
60
+ It also knows which sources matter: Semantic Scholar rate-limits aggressively without an API key, so its failure never turns a real answer into "inconclusive" — only the primary sources do.
61
+
62
+ **2. It refuses to guess.**
63
+ Ask for BibTeX from a slightly-wrong title and most tools hand back the nearest hit. Silently citing the *wrong* paper is worse than citing none, so a weak match returns the candidate and stops:
64
+
65
+ ```console
66
+ $ scholarcheck bibtex "Deep Residual Learning for Image Recognition in Medicine"
67
+ No confident match (best term coverage only 62%). Refusing to emit a possibly wrong entry.
68
+ Closest candidate:
69
+ Deep Residual Learning for Image Recognition (2016, CVPR) doi:10.1109/CVPR.2016.90
70
+ -> If that is the paper, re-run with its DOI: scholarcheck bibtex "<DOI>".
71
+ ```
72
+
73
+ The same refusal applies when the sources themselves are unavailable, which is
74
+ when a wrong entry is most likely — the "best" match would then be whichever
75
+ paper happened to be reachable:
76
+
77
+ ```console
78
+ $ scholarcheck bibtex "Deep Residual Learning for Image Recognition in Medicine"
79
+ INCONCLUSIVE - a primary source could not be reached, so no entry is emitted for: ...
80
+ Could not reach: api.openalex.org: HTTP 429
81
+ (the partial search's best candidate was 50% coverage - not enough to stand on
82
+ while sources are down)
83
+ ```
84
+
85
+ **3. An identifier is resolved, not searched.**
86
+ `verify "arXiv:1906.08253"` looks the identifier up directly. Feeding it to a
87
+ title matcher would return whatever paper happens to share those digits and
88
+ then score it as a mismatch — which reads as *"this citation is fake"* when the
89
+ truth is that the query was never looked up properly.
90
+
91
+ **4. Recency is a separate command, on purpose.**
92
+ Relevance ranking systematically favours highly-cited older work, which is exactly wrong when you are checking whether someone *just* published your idea. `latest` filters by recency as well as relevance.
93
+
94
+ ## Install
95
+
96
+ ```bash
97
+ pip install git+https://github.com/GuoCheng24/scholarcheck
98
+ ```
99
+
100
+ Or clone and `pip install -e .` if you would rather read the source first — it is
101
+ one file.
102
+
103
+ **No dependencies.** Standard library plus `curl`. Nothing to break, nothing to
104
+ audit, and nothing that needs an API key: every source it queries is open.
105
+
106
+ <sub>Not on PyPI yet, so the git URL above is the install line that works today.
107
+ When it lands, `pip install scholarcheck` will too.</sub>
108
+
109
+ ## Commands
110
+
111
+ | | |
112
+ |---|---|
113
+ | `verify "<title/DOI/arXiv id>"` | Is this citation real? An identifier resolves exactly; a title is matched by term coverage |
114
+ | `bibtex "<DOI/title>"` | A BibTeX entry — refuses to guess on a weak match |
115
+ | `search "<keywords>"` | Multi-source search, re-ranked by term overlap |
116
+ | `latest "<keywords>"` | Recent work only — relevance **and** recency |
117
+ | `priorart "<claim>"` | Nearest N real papers for a claim, plus a checklist for judging whether it is already taken |
118
+ | `citedby "<DOI/title>"` | What cited this paper — has someone already extended it? |
119
+ | `journal "<name>"` | Live journal metrics, instead of quoting an impact factor from memory |
120
+ | `injournal "<name>"` | Recent papers from one journal, to study its actual conventions |
121
+ | `fetch "<DOI/arXiv id>"` | Download the open-access PDF so a claim can be checked in full text |
122
+
123
+ Add `--json` to any command for structured output, `-n` for the number of results, `--since YYYY` to bound the year.
124
+
125
+ ## Use as a library
126
+
127
+ ```python
128
+ from scholarcheck import verify_citation, get_bibtex, NET_ERRORS
129
+
130
+ paper, confidence = verify_citation("Attention Is All You Need")
131
+ if paper is None and NET_ERRORS:
132
+ ... # could not check — not evidence of anything
133
+ elif confidence >= 0.75:
134
+ print(get_bibtex(paper["doi"]))
135
+ ```
136
+
137
+ ## Configuration
138
+
139
+ All optional:
140
+
141
+ | variable | effect |
142
+ |---|---|
143
+ | `SCHOLARCHECK_MAILTO` | your email — joins OpenAlex's polite pool, giving better rate limits |
144
+ | `SCHOLARCHECK_S2KEY` | Semantic Scholar API key (free) — avoids the frequent 429s |
145
+ | `SCHOLARCHECK_PROXY` | e.g. `socks5h://127.0.0.1:1080`; default is a direct connection |
146
+
147
+ Proxy behaviour is decided **solely** by `SCHOLARCHECK_PROXY`. Inherited `http_proxy` / `all_proxy` variables are stripped before each request, so the tool behaves the same on every machine.
148
+
149
+ ## What it can and cannot tell you
150
+
151
+ **A match confirms the paper exists — not that the metadata you have is right.**
152
+ Bibliographic databases often hold several records for one work: a preprint, a
153
+ conference version, a publisher deposit. `verify` returns whichever record
154
+ matched best, so the year and venue you see may belong to a different record
155
+ than the one you meant to cite. Check them; the DOI is the reliable part.
156
+
157
+ **"NOT FOUND" is strong evidence, not proof.** Very new work, non-English
158
+ venues and some book chapters are indexed poorly. When it matters, run
159
+ `search` with looser keywords before concluding a reference is invented.
160
+
161
+ ## Notes from real use
162
+
163
+ - **Feed focused keywords, not whole sentences.** A long claim drags in off-topic papers; two or three precise terms work far better.
164
+ - **`search` favours highly-cited older work.** That is what relevance ranking does. Use `latest` when the question is "has this been done recently?"
165
+ - **A title-only judgement is not a prior-art check.** For the closest candidates, `fetch` the PDF and read it.
166
+
167
+ ## License
168
+
169
+ MIT © Guo Cheng
170
+
171
+ ## 关于那行 star 提示
172
+
173
+ 跑命令时,`scholarcheck` 会在**第 5 次和第 25 次**往 stderr 写一行,提一句这个仓库在哪。**一辈子只有这两次**,此外再不出声。
174
+
175
+ 它不会出现在:管道或重定向里(stderr 不是终端就直接返回,连计数文件都不建)、CI 环境里(`CI` / `GITHUB_ACTIONS`)。它写的是 stderr 而非 stdout,所以不会污染你的数据输出;它包在 `try/finally` 里且吞掉自身所有异常,**不会改变退出码,也不会影响结果**。
176
+
177
+ 永久关掉:
178
+
179
+ ```bash
180
+ export SCHOLARCHECK_NO_NUDGE=1
181
+ ```
182
+
183
+ 计数存在 `$XDG_STATE_HOME/scholarcheck/usage.json`(默认 `~/.local/state/scholarcheck/usage.json`),删掉即重置。
@@ -0,0 +1,163 @@
1
+ # scholarcheck
2
+
3
+ [![test](https://github.com/GuoCheng24/scholarcheck/actions/workflows/test.yml/badge.svg)](https://github.com/GuoCheng24/scholarcheck/actions/workflows/test.yml) [![python](https://img.shields.io/badge/python-3.9%2B-blue)](https://www.python.org/) [![license](https://img.shields.io/badge/license-MIT-green)](LICENSE)
4
+
5
+ **Stop hallucinated citations.** Verify any reference against real metadata — from the command line, with zero dependencies.
6
+
7
+ <p align="center">
8
+ <img src="docs/three-states.png" width="100%">
9
+ </p>
10
+
11
+ <sub>The figure above is generated by <a href="docs/three-states_figure.py">docs/three-states_figure.py</a> — <code>pip install git+https://github.com/GuoCheng24/sciglyph</code> and run it to reproduce <code>docs/three-states.png</code> byte for byte.</sub>
12
+
13
+
14
+ Language models invent plausible-looking papers: right-sounding title, plausible authors, a DOI that resolves to nothing. `scholarcheck` answers one question honestly — **does this paper actually exist?** — by querying OpenAlex, Semantic Scholar, Crossref and arXiv directly.
15
+
16
+ ```console
17
+ $ scholarcheck verify "Deep Residual Learning for Image Recognition"
18
+ MATCH (high confidence) [query term coverage = 100%]
19
+ Deep Residual Learning for Image Recognition (2016, conference-paper; cited=226875) doi:10.1109/cvpr.2016.90
20
+ Kaiming He, Xiangyu Zhang, Shaoqing Ren et al.
21
+
22
+ $ scholarcheck verify "Quantum Topological Radiomics for Zebra Diagnosis in Martian Cohorts"
23
+ NOT FOUND in any of the four sources -> this citation is very likely hallucinated
24
+ ```
25
+
26
+ ## Why not just ask an AI assistant?
27
+
28
+ Because an assistant answers from memory, and memory is exactly what fails here. Three design choices make this different:
29
+
30
+ **1. It says "I could not check" instead of "it is fake."**
31
+ A verifier that reports a network outage as *hallucinated* is worse than no verifier. `scholarcheck` tracks every failed request and distinguishes the two:
32
+
33
+ ```console
34
+ $ scholarcheck verify "Attention Is All You Need" # with the network down
35
+ INCONCLUSIVE - could not query the sources, so nothing can be said about: Attention Is All You Need
36
+ Could not reach: api.openalex.org: curl: (7) Connection refused
37
+ (no proxy set; if your network needs one, set SCHOLARCHECK_PROXY)
38
+ ```
39
+
40
+ It also knows which sources matter: Semantic Scholar rate-limits aggressively without an API key, so its failure never turns a real answer into "inconclusive" — only the primary sources do.
41
+
42
+ **2. It refuses to guess.**
43
+ Ask for BibTeX from a slightly-wrong title and most tools hand back the nearest hit. Silently citing the *wrong* paper is worse than citing none, so a weak match returns the candidate and stops:
44
+
45
+ ```console
46
+ $ scholarcheck bibtex "Deep Residual Learning for Image Recognition in Medicine"
47
+ No confident match (best term coverage only 62%). Refusing to emit a possibly wrong entry.
48
+ Closest candidate:
49
+ Deep Residual Learning for Image Recognition (2016, CVPR) doi:10.1109/CVPR.2016.90
50
+ -> If that is the paper, re-run with its DOI: scholarcheck bibtex "<DOI>".
51
+ ```
52
+
53
+ The same refusal applies when the sources themselves are unavailable, which is
54
+ when a wrong entry is most likely — the "best" match would then be whichever
55
+ paper happened to be reachable:
56
+
57
+ ```console
58
+ $ scholarcheck bibtex "Deep Residual Learning for Image Recognition in Medicine"
59
+ INCONCLUSIVE - a primary source could not be reached, so no entry is emitted for: ...
60
+ Could not reach: api.openalex.org: HTTP 429
61
+ (the partial search's best candidate was 50% coverage - not enough to stand on
62
+ while sources are down)
63
+ ```
64
+
65
+ **3. An identifier is resolved, not searched.**
66
+ `verify "arXiv:1906.08253"` looks the identifier up directly. Feeding it to a
67
+ title matcher would return whatever paper happens to share those digits and
68
+ then score it as a mismatch — which reads as *"this citation is fake"* when the
69
+ truth is that the query was never looked up properly.
70
+
71
+ **4. Recency is a separate command, on purpose.**
72
+ Relevance ranking systematically favours highly-cited older work, which is exactly wrong when you are checking whether someone *just* published your idea. `latest` filters by recency as well as relevance.
73
+
74
+ ## Install
75
+
76
+ ```bash
77
+ pip install git+https://github.com/GuoCheng24/scholarcheck
78
+ ```
79
+
80
+ Or clone and `pip install -e .` if you would rather read the source first — it is
81
+ one file.
82
+
83
+ **No dependencies.** Standard library plus `curl`. Nothing to break, nothing to
84
+ audit, and nothing that needs an API key: every source it queries is open.
85
+
86
+ <sub>Not on PyPI yet, so the git URL above is the install line that works today.
87
+ When it lands, `pip install scholarcheck` will too.</sub>
88
+
89
+ ## Commands
90
+
91
+ | | |
92
+ |---|---|
93
+ | `verify "<title/DOI/arXiv id>"` | Is this citation real? An identifier resolves exactly; a title is matched by term coverage |
94
+ | `bibtex "<DOI/title>"` | A BibTeX entry — refuses to guess on a weak match |
95
+ | `search "<keywords>"` | Multi-source search, re-ranked by term overlap |
96
+ | `latest "<keywords>"` | Recent work only — relevance **and** recency |
97
+ | `priorart "<claim>"` | Nearest N real papers for a claim, plus a checklist for judging whether it is already taken |
98
+ | `citedby "<DOI/title>"` | What cited this paper — has someone already extended it? |
99
+ | `journal "<name>"` | Live journal metrics, instead of quoting an impact factor from memory |
100
+ | `injournal "<name>"` | Recent papers from one journal, to study its actual conventions |
101
+ | `fetch "<DOI/arXiv id>"` | Download the open-access PDF so a claim can be checked in full text |
102
+
103
+ Add `--json` to any command for structured output, `-n` for the number of results, `--since YYYY` to bound the year.
104
+
105
+ ## Use as a library
106
+
107
+ ```python
108
+ from scholarcheck import verify_citation, get_bibtex, NET_ERRORS
109
+
110
+ paper, confidence = verify_citation("Attention Is All You Need")
111
+ if paper is None and NET_ERRORS:
112
+ ... # could not check — not evidence of anything
113
+ elif confidence >= 0.75:
114
+ print(get_bibtex(paper["doi"]))
115
+ ```
116
+
117
+ ## Configuration
118
+
119
+ All optional:
120
+
121
+ | variable | effect |
122
+ |---|---|
123
+ | `SCHOLARCHECK_MAILTO` | your email — joins OpenAlex's polite pool, giving better rate limits |
124
+ | `SCHOLARCHECK_S2KEY` | Semantic Scholar API key (free) — avoids the frequent 429s |
125
+ | `SCHOLARCHECK_PROXY` | e.g. `socks5h://127.0.0.1:1080`; default is a direct connection |
126
+
127
+ Proxy behaviour is decided **solely** by `SCHOLARCHECK_PROXY`. Inherited `http_proxy` / `all_proxy` variables are stripped before each request, so the tool behaves the same on every machine.
128
+
129
+ ## What it can and cannot tell you
130
+
131
+ **A match confirms the paper exists — not that the metadata you have is right.**
132
+ Bibliographic databases often hold several records for one work: a preprint, a
133
+ conference version, a publisher deposit. `verify` returns whichever record
134
+ matched best, so the year and venue you see may belong to a different record
135
+ than the one you meant to cite. Check them; the DOI is the reliable part.
136
+
137
+ **"NOT FOUND" is strong evidence, not proof.** Very new work, non-English
138
+ venues and some book chapters are indexed poorly. When it matters, run
139
+ `search` with looser keywords before concluding a reference is invented.
140
+
141
+ ## Notes from real use
142
+
143
+ - **Feed focused keywords, not whole sentences.** A long claim drags in off-topic papers; two or three precise terms work far better.
144
+ - **`search` favours highly-cited older work.** That is what relevance ranking does. Use `latest` when the question is "has this been done recently?"
145
+ - **A title-only judgement is not a prior-art check.** For the closest candidates, `fetch` the PDF and read it.
146
+
147
+ ## License
148
+
149
+ MIT © Guo Cheng
150
+
151
+ ## 关于那行 star 提示
152
+
153
+ 跑命令时,`scholarcheck` 会在**第 5 次和第 25 次**往 stderr 写一行,提一句这个仓库在哪。**一辈子只有这两次**,此外再不出声。
154
+
155
+ 它不会出现在:管道或重定向里(stderr 不是终端就直接返回,连计数文件都不建)、CI 环境里(`CI` / `GITHUB_ACTIONS`)。它写的是 stderr 而非 stdout,所以不会污染你的数据输出;它包在 `try/finally` 里且吞掉自身所有异常,**不会改变退出码,也不会影响结果**。
156
+
157
+ 永久关掉:
158
+
159
+ ```bash
160
+ export SCHOLARCHECK_NO_NUDGE=1
161
+ ```
162
+
163
+ 计数存在 `$XDG_STATE_HOME/scholarcheck/usage.json`(默认 `~/.local/state/scholarcheck/usage.json`),删掉即重置。
@@ -0,0 +1,33 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "scholarcheck"
7
+ version = "0.1.0"
8
+ description = "Verify citations against real metadata - stop hallucinated references. Zero dependencies."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Guo Cheng" }]
13
+ keywords = ["citations", "bibtex", "openalex", "crossref", "arxiv",
14
+ "literature-review", "hallucination", "research-tools", "doi"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Environment :: Console",
18
+ "Intended Audience :: Science/Research",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Topic :: Scientific/Engineering",
22
+ ]
23
+ dependencies = []
24
+
25
+ [project.scripts]
26
+ scholarcheck = "scholarcheck.cli:main"
27
+
28
+ [project.urls]
29
+ Homepage = "https://github.com/GuoCheng24/scholarcheck"
30
+ Issues = "https://github.com/GuoCheng24/scholarcheck/issues"
31
+
32
+ [tool.setuptools.packages.find]
33
+ include = ["scholarcheck*"]
@@ -0,0 +1,50 @@
1
+ """scholarcheck - verifiable literature grounding from the command line.
2
+
3
+ Never cite a paper that does not exist. Every answer is backed by live
4
+ metadata from OpenAlex, Semantic Scholar, Crossref and arXiv.
5
+
6
+ Command line::
7
+
8
+ scholarcheck verify "Attention Is All You Need"
9
+ scholarcheck bibtex "10.1038/s41586-025-10014-0"
10
+ scholarcheck priorart "conformal risk control" -n 6
11
+
12
+ As a library::
13
+
14
+ from scholarcheck import verify_citation, get_bibtex, search
15
+ paper, confidence = verify_citation("Attention Is All You Need")
16
+ """
17
+
18
+ from .cli import (
19
+ multi_search as search,
20
+ latest,
21
+ bibtex as get_bibtex,
22
+ best_match,
23
+ citedby_openalex as cited_by,
24
+ journal_lookup as journal,
25
+ injournal,
26
+ resolve_pdf,
27
+ download_pdf,
28
+ NET_ERRORS,
29
+ )
30
+
31
+ __version__ = "0.1.0"
32
+
33
+
34
+ def verify_citation(query, n=5):
35
+ """Check whether a citation refers to a real paper.
36
+
37
+ Returns ``(paper, confidence)``, where confidence is the fraction of the
38
+ query's content words covered by the matched title: >=0.75 is a confident
39
+ match, <0.45 means the citation is very likely hallucinated. ``paper`` is
40
+ None when nothing matched.
41
+
42
+ An empty result together with a non-empty :data:`NET_ERRORS` means the
43
+ sources could not be reached - that is *not* evidence the paper is fake.
44
+ """
45
+ return best_match(query, n)
46
+
47
+
48
+ __all__ = ["search", "latest", "get_bibtex", "best_match", "cited_by",
49
+ "journal", "injournal", "resolve_pdf", "download_pdf",
50
+ "verify_citation", "NET_ERRORS", "__version__"]
@@ -0,0 +1,58 @@
1
+ """Mention the repo once or twice, to people who are actually using this.
2
+
3
+ Deliberately quiet: never on the first run, never when stderr is not a
4
+ terminal (so piped and redirected output stays clean), never in CI, and
5
+ never more than twice in the lifetime of an install. `SCHOLARCHECK_NO_NUDGE=1`
6
+ turns it off for good.
7
+ """
8
+ import os
9
+ import sys
10
+ import json
11
+ from pathlib import Path
12
+
13
+ REPO = "GuoCheng24/scholarcheck"
14
+ _SHOW_AT = (5, 25) # run counts at which we say something
15
+ _ENV_OFF = "SCHOLARCHECK_NO_NUDGE"
16
+
17
+
18
+ def _state_path():
19
+ base = os.environ.get("XDG_STATE_HOME") or (Path.home() / ".local" / "state")
20
+ return Path(base) / "scholarcheck" / "usage.json"
21
+
22
+
23
+ def _quiet():
24
+ if os.environ.get(_ENV_OFF):
25
+ return True
26
+ if os.environ.get("CI") or os.environ.get("GITHUB_ACTIONS"):
27
+ return True
28
+ # Not a terminal means someone is piping or redirecting us; stay out of it.
29
+ return not (hasattr(sys.stderr, "isatty") and sys.stderr.isatty())
30
+
31
+
32
+ def record_run():
33
+ """Count this run and, at two points, print a single line to stderr.
34
+
35
+ Any failure here is swallowed: a nudge must never break the tool or
36
+ change its exit status.
37
+ """
38
+ if _quiet():
39
+ return
40
+ try:
41
+ p = _state_path()
42
+ try:
43
+ data = json.loads(p.read_text())
44
+ except Exception:
45
+ data = {}
46
+ n = int(data.get("runs", 0)) + 1
47
+ data["runs"] = n
48
+ p.parent.mkdir(parents=True, exist_ok=True)
49
+ p.write_text(json.dumps(data))
50
+ if n in _SHOW_AT:
51
+ print(
52
+ "\n── scholarcheck has been useful " + str(n) + " times. If it saved you time,\n"
53
+ " a star helps other people find it: https://github.com/" + REPO + "\n"
54
+ " (silence this with SCHOLARCHECK_NO_NUDGE=1)",
55
+ file=sys.stderr,
56
+ )
57
+ except Exception:
58
+ pass