paperstack-cli 0.3.1__tar.gz → 0.4.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.
- paperstack_cli-0.4.0/PKG-INFO +203 -0
- paperstack_cli-0.3.1/PKG-INFO → paperstack_cli-0.4.0/README.md +60 -20
- {paperstack_cli-0.3.1 → paperstack_cli-0.4.0}/pyproject.toml +22 -5
- paperstack_cli-0.4.0/scripts/build/vendor/marked.LICENSE +36 -0
- paperstack_cli-0.4.0/src/paperstack/arxiv.py +125 -0
- {paperstack_cli-0.3.1 → paperstack_cli-0.4.0}/src/paperstack/citations.py +2 -3
- {paperstack_cli-0.3.1 → paperstack_cli-0.4.0}/src/paperstack/cli.py +269 -40
- paperstack_cli-0.4.0/src/paperstack/content/arxiv_pdf.py +253 -0
- {paperstack_cli-0.3.1 → paperstack_cli-0.4.0}/src/paperstack/corpora.py +27 -3
- paperstack_cli-0.4.0/src/paperstack/credentials.py +147 -0
- {paperstack_cli-0.3.1 → paperstack_cli-0.4.0}/src/paperstack/dblp_build.py +3 -1
- {paperstack_cli-0.3.1 → paperstack_cli-0.4.0}/src/paperstack/dblp_index.py +27 -53
- {paperstack_cli-0.3.1 → paperstack_cli-0.4.0}/src/paperstack/entrypoint.py +14 -1
- {paperstack_cli-0.3.1 → paperstack_cli-0.4.0}/src/paperstack/metadata.py +20 -8
- paperstack_cli-0.4.0/src/paperstack/semantic_scholar.py +175 -0
- {paperstack_cli-0.3.1 → paperstack_cli-0.4.0}/src/paperstack/viewer.py +38 -10
- paperstack_cli-0.3.1/README.md +0 -126
- paperstack_cli-0.3.1/src/paperstack/content/arxiv_pdf.py +0 -103
- {paperstack_cli-0.3.1 → paperstack_cli-0.4.0}/.gitignore +0 -0
- {paperstack_cli-0.3.1 → paperstack_cli-0.4.0}/LICENSE +0 -0
- {paperstack_cli-0.3.1 → paperstack_cli-0.4.0}/scripts/build/site/app.js +0 -0
- {paperstack_cli-0.3.1 → paperstack_cli-0.4.0}/scripts/build/site/entry.html +0 -0
- {paperstack_cli-0.3.1 → paperstack_cli-0.4.0}/scripts/build/site/favicon.svg +0 -0
- {paperstack_cli-0.3.1 → paperstack_cli-0.4.0}/scripts/build/site/index.html +0 -0
- {paperstack_cli-0.3.1 → paperstack_cli-0.4.0}/scripts/build/site/style.css +0 -0
- {paperstack_cli-0.3.1 → paperstack_cli-0.4.0}/scripts/build/vendor/marked.min.js +0 -0
- {paperstack_cli-0.3.1 → paperstack_cli-0.4.0}/src/paperstack/__init__.py +0 -0
- {paperstack_cli-0.3.1 → paperstack_cli-0.4.0}/src/paperstack/content/__init__.py +0 -0
- {paperstack_cli-0.3.1 → paperstack_cli-0.4.0}/src/paperstack/content/arxiv_source.py +0 -0
- {paperstack_cli-0.3.1 → paperstack_cli-0.4.0}/src/paperstack/content/vendor/latexpand +0 -0
- {paperstack_cli-0.3.1 → paperstack_cli-0.4.0}/src/paperstack/content/vendor/latexpand.LICENSE +0 -0
- {paperstack_cli-0.3.1 → paperstack_cli-0.4.0}/src/paperstack/dblp_catalog.py +0 -0
- {paperstack_cli-0.3.1 → paperstack_cli-0.4.0}/src/paperstack/entry_types.py +0 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: paperstack-cli
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Review, inspect, and retrieve research sources from one CLI
|
|
5
|
+
Project-URL: Repository, https://github.com/MilkClouds/paperstack
|
|
6
|
+
Project-URL: Issues, https://github.com/MilkClouds/paperstack/issues
|
|
7
|
+
License-Expression: Apache-2.0
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
+
Requires-Python: >=3.11
|
|
15
|
+
Requires-Dist: filelock>=3.20
|
|
16
|
+
Requires-Dist: polars>=1.43
|
|
17
|
+
Requires-Dist: python-dotenv>=1.2.2
|
|
18
|
+
Requires-Dist: pyyaml>=6
|
|
19
|
+
Provides-Extra: pdf
|
|
20
|
+
Requires-Dist: pdf-inspector<2,>=1.17; extra == 'pdf'
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# Paperstack
|
|
24
|
+
|
|
25
|
+
[](https://github.com/MilkClouds/paperstack/actions/workflows/ci.yml) [](https://pypi.org/project/paperstack-cli/) [](https://pypi.org/project/paperstack-cli/) [](https://github.com/MilkClouds/paperstack/blob/main/LICENSE)
|
|
26
|
+
|
|
27
|
+
Paperstack is an open-source CLI for maintaining, querying, and publishing an independently authored research corpus.
|
|
28
|
+
The software and corpus are separate: Paperstack can be public while each corpus and its editorial judgments remain
|
|
29
|
+
under its maintainer's control.
|
|
30
|
+
|
|
31
|
+
It can search typed reviews, initialize and validate entries, inspect external paper metadata, read arXiv sources,
|
|
32
|
+
manage a selected-venue DBLP index, and build a static viewer. A synthetic corpus lives in
|
|
33
|
+
[`examples/corpus`](https://github.com/MilkClouds/paperstack/tree/main/examples/corpus); no assessment of real work is
|
|
34
|
+
bundled with the package.
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
uv tool install paperstack-cli
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
PDF conversion is optional:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
uv tool install 'paperstack-cli[pdf]'
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The PDF extra uses `pdf-inspector` for native extraction and selective OCR.
|
|
49
|
+
Clean text PDFs need no external runtime, while routed OCR pages require the pinned PDFium and ONNX Runtime libraries described in the [OCR runtime guide](https://github.com/firecrawl/pdf-inspector/blob/main/docs/ocr-runtime.md).
|
|
50
|
+
The first routed page downloads and verifies the pinned OCR model set unless an offline model directory is configured.
|
|
51
|
+
When the shared libraries are not on the platform search path, point `pdf-inspector` at the extracted files:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
export PDFIUM_LIB_PATH=/absolute/path/to/libpdfium.so
|
|
55
|
+
export ORT_DYLIB_PATH=/absolute/path/to/libonnxruntime.so
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The runtime guide lists the matching downloads and filenames for Linux, macOS, and Windows.
|
|
59
|
+
|
|
60
|
+
## AI agent skill
|
|
61
|
+
|
|
62
|
+
Paperstack ships an [Agent Skills](https://agentskills.io)-compatible skill for Codex, Claude Code, and other
|
|
63
|
+
compatible agents. Install the CLI first, then add the skill globally with the open
|
|
64
|
+
[`skills`](https://github.com/vercel-labs/skills) CLI:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npx skills add MilkClouds/paperstack --skill paperstack -g
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The installer detects supported agents such as Codex and Claude Code; pass `-a codex -a claude-code` to target them
|
|
71
|
+
explicitly, or omit `-g` for a project-local installation. The shared [`SKILL.md`](skills/paperstack/SKILL.md) keeps
|
|
72
|
+
corpus selection, source inspection, entry authoring, validation, and viewer workflows consistent without exposing a
|
|
73
|
+
private corpus. Use `npx skills update paperstack -g` to update it later.
|
|
74
|
+
|
|
75
|
+
## Choose a corpus
|
|
76
|
+
|
|
77
|
+
Register either a local working tree or a GitHub repository. The first registered corpus becomes active.
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
paperstack corpus add work --path ~/research/reviews
|
|
81
|
+
paperstack corpus init new --path ~/research/new-corpus
|
|
82
|
+
paperstack corpus add private --repo OWNER/private-reviews
|
|
83
|
+
paperstack corpus list
|
|
84
|
+
paperstack corpus use private
|
|
85
|
+
paperstack corpus remove work
|
|
86
|
+
paperstack corpus remove private --purge-cache --yes
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Private GitHub repositories use the existing `gh` login without storing its token:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
gh auth login
|
|
93
|
+
paperstack review sync
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`PAPERSTACK_DIR` and `PAPERSTACK_REPO` remain available for automation. Explicit environment variables take priority,
|
|
97
|
+
then a surrounding corpus working tree, then the active profile. Profiles are stored in
|
|
98
|
+
`${XDG_CONFIG_HOME:-~/.config}/paperstack/config.json`.
|
|
99
|
+
|
|
100
|
+
Provider credentials can be stored once without adding them to a project `.env`:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
paperstack config set semantic-scholar.api-key
|
|
104
|
+
paperstack config status
|
|
105
|
+
paperstack config paths
|
|
106
|
+
paperstack config unset semantic-scholar.api-key
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`set` prompts without echoing the value; use `--stdin` to pipe it from a password manager. Credentials are written
|
|
110
|
+
atomically to `${XDG_CONFIG_HOME:-~/.config}/paperstack/credentials.json`, with mode `0600` on POSIX systems.
|
|
111
|
+
Exported environment variables take priority, followed by the nearest `.env`, then the credential store. GitHub
|
|
112
|
+
Actions can therefore keep using repository secrets through environment variables.
|
|
113
|
+
|
|
114
|
+
## Review corpus
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
paperstack review show example2026paperstack --brief
|
|
118
|
+
paperstack review show arxiv:2410.24164 --json
|
|
119
|
+
paperstack review list --quality good --tag vla
|
|
120
|
+
paperstack review search "flow matching"
|
|
121
|
+
paperstack review init <key> --id arxiv:NNNN.NNNNN --title "Verbatim title" --editor <name>
|
|
122
|
+
paperstack review check --style
|
|
123
|
+
paperstack review audit
|
|
124
|
+
paperstack review citations --fetch
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Paper entries, talks, and posts use separate contracts under `entries/{papers,talks,posts}/`. The complete schemas,
|
|
128
|
+
writing guide, and source-reading workflow are in
|
|
129
|
+
[Entry authoring](https://github.com/MilkClouds/paperstack/blob/main/docs/ENTRIES.md).
|
|
130
|
+
|
|
131
|
+
## Inspect papers
|
|
132
|
+
|
|
133
|
+
These commands use external source records and do not select a citation or make an editorial judgment:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
paperstack paper search "Attention Is All You Need" --source dblp
|
|
137
|
+
paperstack paper search "robot learning" --source semantic-scholar --year 2024-2026
|
|
138
|
+
paperstack paper search 'ti:"robot learning"' --source arxiv --category cs.RO --sort date
|
|
139
|
+
paperstack paper metadata arxiv:2106.09685
|
|
140
|
+
paperstack paper metadata doi:10.1109/CVPR.2016.90 --source crossref
|
|
141
|
+
paperstack paper citations arxiv:2106.09685 --limit 50
|
|
142
|
+
paperstack paper references doi:10.48550/arXiv.2106.09685 --limit 50
|
|
143
|
+
paperstack paper read arxiv:2604.23073 --outline
|
|
144
|
+
paperstack paper read arxiv:2604.23073 --section 6
|
|
145
|
+
paperstack paper pdf arxiv:2602.09017
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`metadata` accepts `arxiv:`, `doi:`, `dblp:`, and `openreview:` references. `read` and `pdf` require an `arxiv:`
|
|
149
|
+
reference. `authors`, `citations`, and `references` use Semantic Scholar and also accept its `s2:`, `corpus:`, `acl:`,
|
|
150
|
+
`pmid:`, and `mag:` identifiers. Structured commands expose scoped `--json` flags; networked commands expose scoped
|
|
151
|
+
`--offline` flags. Paperstack reports source records but does not synthesize a citation entry or choose which version
|
|
152
|
+
of a work should be cited.
|
|
153
|
+
|
|
154
|
+
## Build a viewer
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
paperstack viewer build
|
|
158
|
+
paperstack viewer serve --port 8000
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
The viewer is generated from the selected corpus. Source Markdown remains available alongside the rendered pages.
|
|
162
|
+
|
|
163
|
+
## DBLP index
|
|
164
|
+
|
|
165
|
+
The optional selected-venue index accelerates DBLP search and is required by `review audit`:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
paperstack index dblp status
|
|
169
|
+
paperstack index dblp install
|
|
170
|
+
paperstack index dblp update
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Installed snapshots are content-addressed and verified by SHA-256, schema, and embedded metadata before an atomic
|
|
174
|
+
pointer switch. See
|
|
175
|
+
[DBLP snapshot releases](https://github.com/MilkClouds/paperstack/blob/main/docs/DBLP_RELEASES.md) for maintenance.
|
|
176
|
+
|
|
177
|
+
## Configuration
|
|
178
|
+
|
|
179
|
+
| Variable | Purpose |
|
|
180
|
+
|---|---|
|
|
181
|
+
| `PAPERSTACK_DIR` | Explicit local corpus working tree |
|
|
182
|
+
| `PAPERSTACK_REPO` | Explicit GitHub corpus in `OWNER/REPO` form |
|
|
183
|
+
| `PAPERSTACK_TTL` | Remote corpus cache refresh interval; default `3600` seconds |
|
|
184
|
+
| `PAPERSTACK_PAPERS_DIR` | arXiv source and PDF cache |
|
|
185
|
+
| `SEMANTIC_SCHOLAR_API_KEY` | Optional higher-rate Semantic Scholar access |
|
|
186
|
+
| `OPENREVIEW_ACCESS_TOKEN` | Optional OpenReview access cookie value |
|
|
187
|
+
| `XDG_CONFIG_HOME` | Corpus profile and credential configuration root |
|
|
188
|
+
| `XDG_CACHE_HOME` | Remote corpus and paper cache root |
|
|
189
|
+
| `XDG_DATA_HOME` | DBLP index root |
|
|
190
|
+
|
|
191
|
+
The console entry point also loads the nearest `.env` without overriding exported variables. Review exit codes are
|
|
192
|
+
`0` for hits, `1` for no match, `2` for ambiguity, and `3` for unavailable data or invalid configuration.
|
|
193
|
+
|
|
194
|
+
## Development
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
uv run --group lint ruff check .
|
|
198
|
+
uv run --group lint ruff format --check .
|
|
199
|
+
uv run --group test pytest -q
|
|
200
|
+
PAPERSTACK_DIR=examples/corpus uv run paperstack viewer build --output /tmp/paperstack-site
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Paperstack is licensed under Apache-2.0. Corpora are separate works and may use their own terms.
|
|
@@ -1,27 +1,15 @@
|
|
|
1
|
-
Metadata-Version: 2.5
|
|
2
|
-
Name: paperstack-cli
|
|
3
|
-
Version: 0.3.1
|
|
4
|
-
Summary: Review, inspect, and retrieve research sources from one CLI
|
|
5
|
-
Project-URL: Repository, https://github.com/MilkClouds/paperstack
|
|
6
|
-
Project-URL: Issues, https://github.com/MilkClouds/paperstack/issues
|
|
7
|
-
License-File: LICENSE
|
|
8
|
-
Requires-Python: >=3.11.4
|
|
9
|
-
Requires-Dist: polars>=1.43
|
|
10
|
-
Requires-Dist: python-dotenv>=1.2.2
|
|
11
|
-
Requires-Dist: pyyaml>=6
|
|
12
|
-
Provides-Extra: pdf
|
|
13
|
-
Requires-Dist: pymupdf4llm>=0.0.17; extra == 'pdf'
|
|
14
|
-
Description-Content-Type: text/markdown
|
|
15
|
-
|
|
16
1
|
# Paperstack
|
|
17
2
|
|
|
3
|
+
[](https://github.com/MilkClouds/paperstack/actions/workflows/ci.yml) [](https://pypi.org/project/paperstack-cli/) [](https://pypi.org/project/paperstack-cli/) [](https://github.com/MilkClouds/paperstack/blob/main/LICENSE)
|
|
4
|
+
|
|
18
5
|
Paperstack is an open-source CLI for maintaining, querying, and publishing an independently authored research corpus.
|
|
19
6
|
The software and corpus are separate: Paperstack can be public while each corpus and its editorial judgments remain
|
|
20
7
|
under its maintainer's control.
|
|
21
8
|
|
|
22
9
|
It can search typed reviews, initialize and validate entries, inspect external paper metadata, read arXiv sources,
|
|
23
10
|
manage a selected-venue DBLP index, and build a static viewer. A synthetic corpus lives in
|
|
24
|
-
[`examples/corpus`](examples/corpus
|
|
11
|
+
[`examples/corpus`](https://github.com/MilkClouds/paperstack/tree/main/examples/corpus); no assessment of real work is
|
|
12
|
+
bundled with the package.
|
|
25
13
|
|
|
26
14
|
## Install
|
|
27
15
|
|
|
@@ -35,16 +23,45 @@ PDF conversion is optional:
|
|
|
35
23
|
uv tool install 'paperstack-cli[pdf]'
|
|
36
24
|
```
|
|
37
25
|
|
|
26
|
+
The PDF extra uses `pdf-inspector` for native extraction and selective OCR.
|
|
27
|
+
Clean text PDFs need no external runtime, while routed OCR pages require the pinned PDFium and ONNX Runtime libraries described in the [OCR runtime guide](https://github.com/firecrawl/pdf-inspector/blob/main/docs/ocr-runtime.md).
|
|
28
|
+
The first routed page downloads and verifies the pinned OCR model set unless an offline model directory is configured.
|
|
29
|
+
When the shared libraries are not on the platform search path, point `pdf-inspector` at the extracted files:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
export PDFIUM_LIB_PATH=/absolute/path/to/libpdfium.so
|
|
33
|
+
export ORT_DYLIB_PATH=/absolute/path/to/libonnxruntime.so
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The runtime guide lists the matching downloads and filenames for Linux, macOS, and Windows.
|
|
37
|
+
|
|
38
|
+
## AI agent skill
|
|
39
|
+
|
|
40
|
+
Paperstack ships an [Agent Skills](https://agentskills.io)-compatible skill for Codex, Claude Code, and other
|
|
41
|
+
compatible agents. Install the CLI first, then add the skill globally with the open
|
|
42
|
+
[`skills`](https://github.com/vercel-labs/skills) CLI:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npx skills add MilkClouds/paperstack --skill paperstack -g
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The installer detects supported agents such as Codex and Claude Code; pass `-a codex -a claude-code` to target them
|
|
49
|
+
explicitly, or omit `-g` for a project-local installation. The shared [`SKILL.md`](skills/paperstack/SKILL.md) keeps
|
|
50
|
+
corpus selection, source inspection, entry authoring, validation, and viewer workflows consistent without exposing a
|
|
51
|
+
private corpus. Use `npx skills update paperstack -g` to update it later.
|
|
52
|
+
|
|
38
53
|
## Choose a corpus
|
|
39
54
|
|
|
40
55
|
Register either a local working tree or a GitHub repository. The first registered corpus becomes active.
|
|
41
56
|
|
|
42
57
|
```bash
|
|
43
58
|
paperstack corpus add work --path ~/research/reviews
|
|
59
|
+
paperstack corpus init new --path ~/research/new-corpus
|
|
44
60
|
paperstack corpus add private --repo OWNER/private-reviews
|
|
45
61
|
paperstack corpus list
|
|
46
62
|
paperstack corpus use private
|
|
47
63
|
paperstack corpus remove work
|
|
64
|
+
paperstack corpus remove private --purge-cache --yes
|
|
48
65
|
```
|
|
49
66
|
|
|
50
67
|
Private GitHub repositories use the existing `gh` login without storing its token:
|
|
@@ -58,6 +75,20 @@ paperstack review sync
|
|
|
58
75
|
then a surrounding corpus working tree, then the active profile. Profiles are stored in
|
|
59
76
|
`${XDG_CONFIG_HOME:-~/.config}/paperstack/config.json`.
|
|
60
77
|
|
|
78
|
+
Provider credentials can be stored once without adding them to a project `.env`:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
paperstack config set semantic-scholar.api-key
|
|
82
|
+
paperstack config status
|
|
83
|
+
paperstack config paths
|
|
84
|
+
paperstack config unset semantic-scholar.api-key
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`set` prompts without echoing the value; use `--stdin` to pipe it from a password manager. Credentials are written
|
|
88
|
+
atomically to `${XDG_CONFIG_HOME:-~/.config}/paperstack/credentials.json`, with mode `0600` on POSIX systems.
|
|
89
|
+
Exported environment variables take priority, followed by the nearest `.env`, then the credential store. GitHub
|
|
90
|
+
Actions can therefore keep using repository secrets through environment variables.
|
|
91
|
+
|
|
61
92
|
## Review corpus
|
|
62
93
|
|
|
63
94
|
```bash
|
|
@@ -72,7 +103,8 @@ paperstack review citations --fetch
|
|
|
72
103
|
```
|
|
73
104
|
|
|
74
105
|
Paper entries, talks, and posts use separate contracts under `entries/{papers,talks,posts}/`. The complete schemas,
|
|
75
|
-
writing guide, and source-reading workflow are in
|
|
106
|
+
writing guide, and source-reading workflow are in
|
|
107
|
+
[Entry authoring](https://github.com/MilkClouds/paperstack/blob/main/docs/ENTRIES.md).
|
|
76
108
|
|
|
77
109
|
## Inspect papers
|
|
78
110
|
|
|
@@ -80,15 +112,22 @@ These commands use external source records and do not select a citation or make
|
|
|
80
112
|
|
|
81
113
|
```bash
|
|
82
114
|
paperstack paper search "Attention Is All You Need" --source dblp
|
|
115
|
+
paperstack paper search "robot learning" --source semantic-scholar --year 2024-2026
|
|
116
|
+
paperstack paper search 'ti:"robot learning"' --source arxiv --category cs.RO --sort date
|
|
83
117
|
paperstack paper metadata arxiv:2106.09685
|
|
84
118
|
paperstack paper metadata doi:10.1109/CVPR.2016.90 --source crossref
|
|
119
|
+
paperstack paper citations arxiv:2106.09685 --limit 50
|
|
120
|
+
paperstack paper references doi:10.48550/arXiv.2106.09685 --limit 50
|
|
85
121
|
paperstack paper read arxiv:2604.23073 --outline
|
|
86
122
|
paperstack paper read arxiv:2604.23073 --section 6
|
|
87
123
|
paperstack paper pdf arxiv:2602.09017
|
|
88
124
|
```
|
|
89
125
|
|
|
90
126
|
`metadata` accepts `arxiv:`, `doi:`, `dblp:`, and `openreview:` references. `read` and `pdf` require an `arxiv:`
|
|
91
|
-
reference.
|
|
127
|
+
reference. `authors`, `citations`, and `references` use Semantic Scholar and also accept its `s2:`, `corpus:`, `acl:`,
|
|
128
|
+
`pmid:`, and `mag:` identifiers. Structured commands expose scoped `--json` flags; networked commands expose scoped
|
|
129
|
+
`--offline` flags. Paperstack reports source records but does not synthesize a citation entry or choose which version
|
|
130
|
+
of a work should be cited.
|
|
92
131
|
|
|
93
132
|
## Build a viewer
|
|
94
133
|
|
|
@@ -110,7 +149,8 @@ paperstack index dblp update
|
|
|
110
149
|
```
|
|
111
150
|
|
|
112
151
|
Installed snapshots are content-addressed and verified by SHA-256, schema, and embedded metadata before an atomic
|
|
113
|
-
pointer switch. See
|
|
152
|
+
pointer switch. See
|
|
153
|
+
[DBLP snapshot releases](https://github.com/MilkClouds/paperstack/blob/main/docs/DBLP_RELEASES.md) for maintenance.
|
|
114
154
|
|
|
115
155
|
## Configuration
|
|
116
156
|
|
|
@@ -122,7 +162,7 @@ pointer switch. See [DBLP snapshot releases](docs/DBLP_RELEASES.md) for release
|
|
|
122
162
|
| `PAPERSTACK_PAPERS_DIR` | arXiv source and PDF cache |
|
|
123
163
|
| `SEMANTIC_SCHOLAR_API_KEY` | Optional higher-rate Semantic Scholar access |
|
|
124
164
|
| `OPENREVIEW_ACCESS_TOKEN` | Optional OpenReview access cookie value |
|
|
125
|
-
| `XDG_CONFIG_HOME` | Corpus profile configuration root |
|
|
165
|
+
| `XDG_CONFIG_HOME` | Corpus profile and credential configuration root |
|
|
126
166
|
| `XDG_CACHE_HOME` | Remote corpus and paper cache root |
|
|
127
167
|
| `XDG_DATA_HOME` | DBLP index root |
|
|
128
168
|
|
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "paperstack-cli"
|
|
3
|
-
|
|
3
|
+
dynamic = ["version"]
|
|
4
4
|
description = "Review, inspect, and retrieve research sources from one CLI"
|
|
5
5
|
readme = "README.md"
|
|
6
|
-
|
|
6
|
+
license = "Apache-2.0"
|
|
7
|
+
requires-python = ">=3.11"
|
|
8
|
+
classifiers = [
|
|
9
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
10
|
+
"Programming Language :: Python :: 3.11",
|
|
11
|
+
"Programming Language :: Python :: 3.12",
|
|
12
|
+
"Programming Language :: Python :: 3.13",
|
|
13
|
+
"Programming Language :: Python :: 3.14",
|
|
14
|
+
]
|
|
7
15
|
dependencies = [
|
|
16
|
+
"filelock>=3.20",
|
|
8
17
|
"polars>=1.43",
|
|
9
18
|
"python-dotenv>=1.2.2",
|
|
10
19
|
"pyyaml>=6",
|
|
11
20
|
]
|
|
12
21
|
|
|
13
22
|
[project.optional-dependencies]
|
|
14
|
-
pdf = ["
|
|
23
|
+
pdf = ["pdf-inspector>=1.17,<2"]
|
|
15
24
|
|
|
16
25
|
[dependency-groups]
|
|
17
26
|
lint = ["ruff>=0.12"]
|
|
@@ -34,13 +43,21 @@ packages = ["src/paperstack"]
|
|
|
34
43
|
[tool.hatch.build.targets.wheel.force-include]
|
|
35
44
|
"scripts/build/site" = "paperstack/viewer_assets/site"
|
|
36
45
|
"scripts/build/vendor/marked.min.js" = "paperstack/viewer_assets/vendor/marked.min.js"
|
|
46
|
+
"scripts/build/vendor/marked.LICENSE" = "paperstack/viewer_assets/vendor/marked.LICENSE"
|
|
37
47
|
|
|
38
48
|
[tool.hatch.build.targets.sdist]
|
|
39
|
-
include = ["/src/paperstack", "/scripts/build/site", "/scripts/build/vendor/marked.min.js", "/README.md", "/pyproject.toml", "/LICENSE"]
|
|
49
|
+
include = ["/src/paperstack", "/scripts/build/site", "/scripts/build/vendor/marked.min.js", "/scripts/build/vendor/marked.LICENSE", "/README.md", "/pyproject.toml", "/LICENSE"]
|
|
40
50
|
|
|
41
51
|
[tool.ruff]
|
|
42
52
|
line-length = 119
|
|
43
53
|
|
|
54
|
+
[tool.hatch.version]
|
|
55
|
+
source = "vcs"
|
|
56
|
+
|
|
57
|
+
[tool.hatch.version.raw-options]
|
|
58
|
+
tag_regex = "^v(?P<version>\\d+\\.\\d+\\.\\d+(?:[a-zA-Z0-9.-]*)?)$"
|
|
59
|
+
git_describe_command = ["git", "describe", "--dirty", "--tags", "--long", "--match", "v[0-9]*"]
|
|
60
|
+
|
|
44
61
|
[build-system]
|
|
45
|
-
requires = ["hatchling"]
|
|
62
|
+
requires = ["hatchling", "hatch-vcs"]
|
|
46
63
|
build-backend = "hatchling.build"
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
## Marked
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2018+, MarkedJS (https://github.com/markedjs/)
|
|
4
|
+
Copyright (c) 2011-2018, Christopher Jeffrey (https://github.com/chjj/)
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
|
14
|
+
all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
22
|
+
THE SOFTWARE.
|
|
23
|
+
|
|
24
|
+
## Markdown
|
|
25
|
+
|
|
26
|
+
Copyright © 2004, John Gruber
|
|
27
|
+
http://daringfireball.net/
|
|
28
|
+
All rights reserved.
|
|
29
|
+
|
|
30
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
31
|
+
|
|
32
|
+
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
33
|
+
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
34
|
+
* Neither the name “Markdown” nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
|
35
|
+
|
|
36
|
+
This software is provided by the copyright holders and contributors “as is” and any express or implied warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose are disclaimed. In no event shall the copyright owner or contributors be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this software, even if advised of the possibility of such damage.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Bounded arXiv discovery with source-backed metadata."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
import urllib.parse
|
|
7
|
+
import xml.etree.ElementTree as ET
|
|
8
|
+
from datetime import UTC, datetime
|
|
9
|
+
|
|
10
|
+
from . import metadata
|
|
11
|
+
|
|
12
|
+
ATOM = "http://www.w3.org/2005/Atom"
|
|
13
|
+
ARXIV = "http://arxiv.org/schemas/atom"
|
|
14
|
+
NS = {"atom": ATOM, "arxiv": ARXIV}
|
|
15
|
+
MAX_RESULTS = 100
|
|
16
|
+
_CATEGORY = re.compile(r"^[a-z-]+(?:\.[A-Za-z-]+)?$")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _date(value: str, *, end: bool = False) -> str:
|
|
20
|
+
try:
|
|
21
|
+
parsed = datetime.fromisoformat(value)
|
|
22
|
+
except ValueError as exc:
|
|
23
|
+
raise ValueError(f"invalid date {value!r}; use YYYY-MM-DD or ISO 8601") from exc
|
|
24
|
+
if len(value) == 10:
|
|
25
|
+
parsed = parsed.replace(hour=23 if end else 0, minute=59 if end else 0)
|
|
26
|
+
elif parsed.tzinfo is not None:
|
|
27
|
+
parsed = parsed.astimezone(UTC)
|
|
28
|
+
return parsed.strftime("%Y%m%d%H%M")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _query(
|
|
32
|
+
query: str,
|
|
33
|
+
*,
|
|
34
|
+
categories: list[str] | None = None,
|
|
35
|
+
date_from: str | None = None,
|
|
36
|
+
date_to: str | None = None,
|
|
37
|
+
) -> str:
|
|
38
|
+
parts = []
|
|
39
|
+
if query.strip():
|
|
40
|
+
parts.append(f"({query.strip()})")
|
|
41
|
+
if categories:
|
|
42
|
+
invalid = [item for item in categories if not _CATEGORY.fullmatch(item)]
|
|
43
|
+
if invalid:
|
|
44
|
+
raise ValueError(f"invalid arXiv category: {invalid[0]}")
|
|
45
|
+
parts.append("(" + " OR ".join(f"cat:{item}" for item in categories) + ")")
|
|
46
|
+
if date_from or date_to:
|
|
47
|
+
start = _date(date_from) if date_from else "199107010000"
|
|
48
|
+
end = _date(date_to, end=True) if date_to else datetime.now(UTC).strftime("%Y%m%d%H%M")
|
|
49
|
+
if start > end:
|
|
50
|
+
raise ValueError("date-from must not be after date-to")
|
|
51
|
+
parts.append(f"submittedDate:[{start}+TO+{end}]")
|
|
52
|
+
if not parts:
|
|
53
|
+
raise ValueError("arXiv search needs a query, category, or date range")
|
|
54
|
+
return " AND ".join(parts)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _text(entry: ET.Element, name: str) -> str | None:
|
|
58
|
+
value = entry.findtext(name, None, NS)
|
|
59
|
+
return " ".join(value.split()) if value else None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _entry(entry: ET.Element) -> dict:
|
|
63
|
+
raw_id = _text(entry, "atom:id") or ""
|
|
64
|
+
versioned = raw_id.rsplit("/abs/", 1)[-1]
|
|
65
|
+
arxiv_id = re.sub(r"v\d+$", "", versioned)
|
|
66
|
+
links = {node.get("title") or node.get("rel"): node.get("href") for node in entry.findall("atom:link", NS)}
|
|
67
|
+
categories = [node.get("term") for node in entry.findall("atom:category", NS) if node.get("term")]
|
|
68
|
+
primary = entry.find("arxiv:primary_category", NS)
|
|
69
|
+
authors = [_text(node, "atom:name") for node in entry.findall("atom:author", NS)]
|
|
70
|
+
return {
|
|
71
|
+
"id": arxiv_id,
|
|
72
|
+
"versioned_id": versioned,
|
|
73
|
+
"title": _text(entry, "atom:title"),
|
|
74
|
+
"authors": [author for author in authors if author],
|
|
75
|
+
"abstract": _text(entry, "atom:summary"),
|
|
76
|
+
"categories": categories,
|
|
77
|
+
"primary_category": primary.get("term") if primary is not None else (categories[0] if categories else None),
|
|
78
|
+
"published": _text(entry, "atom:published"),
|
|
79
|
+
"updated": _text(entry, "atom:updated"),
|
|
80
|
+
"comment": _text(entry, "arxiv:comment"),
|
|
81
|
+
"journal_ref": _text(entry, "arxiv:journal_ref"),
|
|
82
|
+
"doi": _text(entry, "arxiv:doi"),
|
|
83
|
+
"url": f"https://arxiv.org/abs/{versioned}",
|
|
84
|
+
"pdf_url": links.get("pdf") or f"https://arxiv.org/pdf/{versioned}",
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def parse_feed(raw: bytes | str) -> list[dict]:
|
|
89
|
+
root = ET.fromstring(raw)
|
|
90
|
+
return [_entry(entry) for entry in root.findall("atom:entry", NS)]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def search(
|
|
94
|
+
query: str,
|
|
95
|
+
*,
|
|
96
|
+
categories: list[str] | None = None,
|
|
97
|
+
date_from: str | None = None,
|
|
98
|
+
date_to: str | None = None,
|
|
99
|
+
limit: int = 10,
|
|
100
|
+
sort: str = "relevance",
|
|
101
|
+
) -> dict:
|
|
102
|
+
if not 1 <= limit <= MAX_RESULTS:
|
|
103
|
+
raise ValueError(f"limit must be between 1 and {MAX_RESULTS}")
|
|
104
|
+
if sort not in ("relevance", "date"):
|
|
105
|
+
raise ValueError("sort must be relevance or date")
|
|
106
|
+
built = _query(query, categories=categories, date_from=date_from, date_to=date_to)
|
|
107
|
+
params = urllib.parse.urlencode(
|
|
108
|
+
{
|
|
109
|
+
"search_query": built,
|
|
110
|
+
"max_results": limit,
|
|
111
|
+
"sortBy": "submittedDate" if sort == "date" else "relevance",
|
|
112
|
+
"sortOrder": "descending",
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
params = params.replace("%2BTO%2B", "+TO+")
|
|
116
|
+
url = f"https://export.arxiv.org/api/query?{params}"
|
|
117
|
+
return metadata._safe(
|
|
118
|
+
lambda: metadata._result(
|
|
119
|
+
"arxiv",
|
|
120
|
+
url,
|
|
121
|
+
{"query": built, "matches": parse_feed(metadata.request(url))},
|
|
122
|
+
),
|
|
123
|
+
"arxiv",
|
|
124
|
+
url,
|
|
125
|
+
)
|
|
@@ -3,13 +3,12 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
|
-
import os
|
|
7
6
|
import re
|
|
8
7
|
import urllib.parse
|
|
9
8
|
from datetime import UTC, datetime
|
|
10
9
|
from pathlib import Path
|
|
11
10
|
|
|
12
|
-
from . import metadata
|
|
11
|
+
from . import credentials, metadata
|
|
13
12
|
|
|
14
13
|
S2_BATCH_API = "https://api.semanticscholar.org/graph/v1/paper/batch"
|
|
15
14
|
BATCH_SIZE = 500
|
|
@@ -44,7 +43,7 @@ def fetch(arxiv_ids: list[str]) -> dict[str, int]:
|
|
|
44
43
|
"""Fetch citation counts in aligned Semantic Scholar batches."""
|
|
45
44
|
counts: dict[str, int] = {}
|
|
46
45
|
headers = {"Content-Type": "application/json"}
|
|
47
|
-
if api_key :=
|
|
46
|
+
if api_key := credentials.get(credentials.SEMANTIC_SCHOLAR_API_KEY):
|
|
48
47
|
headers["x-api-key"] = api_key
|
|
49
48
|
|
|
50
49
|
for start in range(0, len(arxiv_ids), BATCH_SIZE):
|