scholarinboxcli 0.1.0__py3-none-any.whl → 0.1.2__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.
- scholarinboxcli/__init__.py +1 -1
- scholarinboxcli/api/client.py +96 -67
- scholarinboxcli/api/endpoints.py +54 -0
- scholarinboxcli/cli.py +11 -505
- scholarinboxcli/commands/__init__.py +1 -0
- scholarinboxcli/commands/auth.py +39 -0
- scholarinboxcli/commands/bookmarks.py +49 -0
- scholarinboxcli/commands/collections.py +135 -0
- scholarinboxcli/commands/common.py +59 -0
- scholarinboxcli/commands/conferences.py +35 -0
- scholarinboxcli/commands/papers.py +88 -0
- scholarinboxcli/formatters/domain_tables.py +122 -0
- scholarinboxcli/formatters/table.py +93 -25
- scholarinboxcli/services/__init__.py +1 -0
- scholarinboxcli/services/collections.py +130 -0
- scholarinboxcli/services/paper_sort.py +54 -0
- {scholarinboxcli-0.1.0.dist-info → scholarinboxcli-0.1.2.dist-info}/METADATA +13 -46
- scholarinboxcli-0.1.2.dist-info/RECORD +23 -0
- scholarinboxcli-0.1.2.dist-info/licenses/LICENSE +21 -0
- scholarinboxcli-0.1.0.dist-info/RECORD +0 -10
- {scholarinboxcli-0.1.0.dist-info → scholarinboxcli-0.1.2.dist-info}/WHEEL +0 -0
- {scholarinboxcli-0.1.0.dist-info → scholarinboxcli-0.1.2.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""Collection name/ID resolution helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from scholarinboxcli.api.client import ApiError, ScholarInboxClient
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def normalize_name(name: str) -> str:
|
|
9
|
+
return name.strip().lower()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def collection_candidates(items: object) -> list[tuple[str, str]]:
|
|
13
|
+
if not isinstance(items, list):
|
|
14
|
+
return []
|
|
15
|
+
candidates: list[tuple[str, str]] = []
|
|
16
|
+
for item in items:
|
|
17
|
+
if isinstance(item, dict):
|
|
18
|
+
name = item.get("name") or item.get("collection_name") or ""
|
|
19
|
+
cid = str(item.get("id") or item.get("collection_id") or "")
|
|
20
|
+
elif isinstance(item, str):
|
|
21
|
+
name = item
|
|
22
|
+
cid = ""
|
|
23
|
+
else:
|
|
24
|
+
continue
|
|
25
|
+
if name:
|
|
26
|
+
candidates.append((name, cid))
|
|
27
|
+
return candidates
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def collection_items_from_response(data: object) -> object:
|
|
31
|
+
if isinstance(data, dict):
|
|
32
|
+
for key in ("collections", "expanded_collections", "collection_names"):
|
|
33
|
+
if key in data:
|
|
34
|
+
return data.get(key)
|
|
35
|
+
return data
|
|
36
|
+
return data
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def collection_candidates_from_map(data: object) -> list[tuple[str, str]]:
|
|
40
|
+
if not isinstance(data, dict):
|
|
41
|
+
return []
|
|
42
|
+
mapping = data.get("collection_names_to_ids_dict")
|
|
43
|
+
if not isinstance(mapping, dict):
|
|
44
|
+
return []
|
|
45
|
+
return [(str(name), str(cid)) for name, cid in mapping.items() if name and cid is not None]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def candidates_have_ids(candidates: list[tuple[str, str]]) -> bool:
|
|
49
|
+
return any(cid for _, cid in candidates)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def match_collection_name(candidates: list[tuple[str, str]], identifier: str) -> str | None:
|
|
53
|
+
target = normalize_name(identifier)
|
|
54
|
+
names = [(name, cid) for name, cid in candidates if name]
|
|
55
|
+
|
|
56
|
+
for name, _ in names:
|
|
57
|
+
if normalize_name(name) == target:
|
|
58
|
+
return name
|
|
59
|
+
|
|
60
|
+
prefix = [c for c in names if normalize_name(c[0]).startswith(target)]
|
|
61
|
+
if len(prefix) == 1:
|
|
62
|
+
return prefix[0][0]
|
|
63
|
+
if len(prefix) > 1:
|
|
64
|
+
names_str = ", ".join([n for n, _ in prefix[:10]])
|
|
65
|
+
raise ApiError(f"Ambiguous collection name. Matches: {names_str}")
|
|
66
|
+
|
|
67
|
+
contains = [c for c in names if target in normalize_name(c[0])]
|
|
68
|
+
if len(contains) == 1:
|
|
69
|
+
return contains[0][0]
|
|
70
|
+
if len(contains) > 1:
|
|
71
|
+
names_str = ", ".join([n for n, _ in contains[:10]])
|
|
72
|
+
raise ApiError(f"Ambiguous collection name. Matches: {names_str}")
|
|
73
|
+
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def resolve_collection_id(client: ScholarInboxClient, identifier: str) -> str:
|
|
78
|
+
"""Resolve numeric IDs directly, otherwise match by collection name."""
|
|
79
|
+
if identifier.isdigit():
|
|
80
|
+
return identifier
|
|
81
|
+
|
|
82
|
+
data = client.collections_list()
|
|
83
|
+
items = collection_items_from_response(data)
|
|
84
|
+
candidates = collection_candidates(items)
|
|
85
|
+
|
|
86
|
+
if not candidates_have_ids(candidates):
|
|
87
|
+
try:
|
|
88
|
+
data = client.collections_expanded()
|
|
89
|
+
items = collection_items_from_response(data)
|
|
90
|
+
candidates = collection_candidates(items)
|
|
91
|
+
except ApiError:
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
if not candidates_have_ids(candidates):
|
|
95
|
+
try:
|
|
96
|
+
data = client.collections_map()
|
|
97
|
+
mapped = collection_candidates_from_map(data)
|
|
98
|
+
if mapped:
|
|
99
|
+
candidates = mapped
|
|
100
|
+
except ApiError:
|
|
101
|
+
pass
|
|
102
|
+
|
|
103
|
+
if not candidates_have_ids(candidates):
|
|
104
|
+
matched = match_collection_name(candidates, identifier)
|
|
105
|
+
if matched:
|
|
106
|
+
return matched
|
|
107
|
+
raise ApiError("Unable to resolve collection name (no IDs available)")
|
|
108
|
+
|
|
109
|
+
candidates = [(name, cid) for name, cid in candidates if cid]
|
|
110
|
+
target = normalize_name(identifier)
|
|
111
|
+
|
|
112
|
+
for name, cid in candidates:
|
|
113
|
+
if normalize_name(name) == target:
|
|
114
|
+
return cid
|
|
115
|
+
|
|
116
|
+
prefix = [c for c in candidates if normalize_name(c[0]).startswith(target)]
|
|
117
|
+
if len(prefix) == 1:
|
|
118
|
+
return prefix[0][1]
|
|
119
|
+
if len(prefix) > 1:
|
|
120
|
+
names = ", ".join([f"{n}({cid})" for n, cid in prefix[:10]])
|
|
121
|
+
raise ApiError(f"Ambiguous collection name. Matches: {names}")
|
|
122
|
+
|
|
123
|
+
contains = [c for c in candidates if target in normalize_name(c[0])]
|
|
124
|
+
if len(contains) == 1:
|
|
125
|
+
return contains[0][1]
|
|
126
|
+
if len(contains) > 1:
|
|
127
|
+
names = ", ".join([f"{n}({cid})" for n, cid in contains[:10]])
|
|
128
|
+
raise ApiError(f"Ambiguous collection name. Matches: {names}")
|
|
129
|
+
|
|
130
|
+
raise ApiError("Collection name not found")
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Helpers for sorting paper-like API responses."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _year_from_paper(paper: dict[str, Any]) -> int:
|
|
10
|
+
year = paper.get("year") or paper.get("publication_year") or paper.get("conference_year")
|
|
11
|
+
if isinstance(year, int):
|
|
12
|
+
return year
|
|
13
|
+
if isinstance(year, float):
|
|
14
|
+
return int(year)
|
|
15
|
+
if isinstance(year, str) and year.isdigit():
|
|
16
|
+
return int(year)
|
|
17
|
+
|
|
18
|
+
publication_date = paper.get("publication_date")
|
|
19
|
+
if isinstance(publication_date, str) and len(publication_date) >= 4 and publication_date[:4].isdigit():
|
|
20
|
+
return int(publication_date[:4])
|
|
21
|
+
if isinstance(publication_date, (int, float)):
|
|
22
|
+
ts = float(publication_date)
|
|
23
|
+
if ts > 10_000_000_000:
|
|
24
|
+
ts /= 1000.0
|
|
25
|
+
try:
|
|
26
|
+
return datetime.fromtimestamp(ts, tz=timezone.utc).year
|
|
27
|
+
except Exception:
|
|
28
|
+
return 0
|
|
29
|
+
return 0
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def sort_paper_response(data: Any, sort_by: str | None, asc: bool) -> Any:
|
|
33
|
+
"""Return a sorted copy of known paper-list structures."""
|
|
34
|
+
if not sort_by:
|
|
35
|
+
return data
|
|
36
|
+
if not isinstance(data, dict):
|
|
37
|
+
return data
|
|
38
|
+
|
|
39
|
+
for key in ("digest_df", "papers", "results", "items", "data"):
|
|
40
|
+
rows = data.get(key)
|
|
41
|
+
if not isinstance(rows, list) or not rows or not isinstance(rows[0], dict):
|
|
42
|
+
continue
|
|
43
|
+
|
|
44
|
+
if sort_by == "year":
|
|
45
|
+
sorted_rows = sorted(rows, key=_year_from_paper, reverse=not asc)
|
|
46
|
+
elif sort_by == "title":
|
|
47
|
+
sorted_rows = sorted(rows, key=lambda p: str(p.get("title") or p.get("paper_title") or "").lower(), reverse=not asc)
|
|
48
|
+
else:
|
|
49
|
+
return data
|
|
50
|
+
|
|
51
|
+
out = dict(data)
|
|
52
|
+
out[key] = sorted_rows
|
|
53
|
+
return out
|
|
54
|
+
return data
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scholarinboxcli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: CLI for Scholar Inbox (authenticated web API)
|
|
5
5
|
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
6
7
|
Keywords: bibliography,cli,research,scholar
|
|
7
8
|
Classifier: Development Status :: 3 - Alpha
|
|
8
9
|
Classifier: Environment :: Console
|
|
@@ -21,7 +22,7 @@ Description-Content-Type: text/markdown
|
|
|
21
22
|
|
|
22
23
|
# scholarinboxcli
|
|
23
24
|
|
|
24
|
-
CLI for Scholar Inbox, for humans and agents alike.
|
|
25
|
+
CLI for [Scholar Inbox](https://scholar-inbox.com), for humans and agents alike.
|
|
25
26
|
|
|
26
27
|
## Installation
|
|
27
28
|
|
|
@@ -121,6 +122,12 @@ scholarinboxcli collection papers 10759
|
|
|
121
122
|
# Similar papers for one or more collections
|
|
122
123
|
scholarinboxcli collection similar 10759 12345
|
|
123
124
|
|
|
125
|
+
# Optional local sorting for display (e.g., newest first)
|
|
126
|
+
scholarinboxcli collection similar "AIAgents" --sort year
|
|
127
|
+
|
|
128
|
+
# Sort ascending instead
|
|
129
|
+
scholarinboxcli collection similar "AIAgents" --sort year --asc
|
|
130
|
+
|
|
124
131
|
# You can also use collection names (case-insensitive). The CLI will
|
|
125
132
|
# automatically fetch collection ID mappings from the API when needed.
|
|
126
133
|
scholarinboxcli collection papers "AIAgents"
|
|
@@ -128,6 +135,7 @@ scholarinboxcli collection similar "AIAgents" "Benchmark"
|
|
|
128
135
|
```
|
|
129
136
|
|
|
130
137
|
Collection name matching is exact → prefix → contains. If multiple matches exist, the CLI reports ambiguity and shows candidate IDs.
|
|
138
|
+
`collection similar` supports client-side sorting with `--sort year|title` and optional `--asc`.
|
|
131
139
|
|
|
132
140
|
## Search
|
|
133
141
|
|
|
@@ -184,53 +192,12 @@ scholarinboxcli collection papers "AIAgents" --json
|
|
|
184
192
|
scholarinboxcli search "diffusion" --json
|
|
185
193
|
```
|
|
186
194
|
|
|
187
|
-
## Tested (2026-02-01)
|
|
188
|
-
|
|
189
|
-
The following commands were exercised against the live API (with a valid magic-link login) to confirm behavior:
|
|
190
|
-
|
|
191
|
-
```bash
|
|
192
|
-
scholarinboxcli --help
|
|
193
|
-
scholarinboxcli auth status --json
|
|
194
|
-
scholarinboxcli digest --date 01-30-2026 --json
|
|
195
|
-
scholarinboxcli trending --category ALL --days 7 --json
|
|
196
|
-
scholarinboxcli search "transformers" --limit 5 --json
|
|
197
|
-
scholarinboxcli semantic "graph neural networks" --limit 5 --json
|
|
198
|
-
scholarinboxcli interactions --type all --json
|
|
199
|
-
scholarinboxcli bookmark list --json
|
|
200
|
-
scholarinboxcli bookmark add 3302478 --json
|
|
201
|
-
scholarinboxcli bookmark remove 3302478 --json
|
|
202
|
-
scholarinboxcli collection list --json
|
|
203
|
-
scholarinboxcli collection list --expanded --json
|
|
204
|
-
scholarinboxcli collection papers "AIAgents" --json
|
|
205
|
-
scholarinboxcli collection similar "AIAgents" --json
|
|
206
|
-
scholarinboxcli conference list --json
|
|
207
|
-
scholarinboxcli conference explore --json
|
|
208
|
-
```
|
|
209
|
-
|
|
210
195
|
## Notes
|
|
211
196
|
|
|
212
197
|
- Some collection mutations (create/rename/delete/add/remove) rely on best-effort endpoints that may change on the service side. If a mutation fails, try again or use the web UI to validate the current behavior.
|
|
198
|
+
- Bookmarks are stored as a dedicated collection named "Bookmarks" in the web app; `bookmark list` pulls that collection via `/api/get_collections`.
|
|
213
199
|
- Similar papers for collections uses the server endpoint used by the web UI. Results typically appear under `digest_df` in JSON responses.
|
|
214
200
|
|
|
215
|
-
##
|
|
216
|
-
|
|
217
|
-
```bash
|
|
218
|
-
# 1) Build sdist + wheel
|
|
219
|
-
uv run --with build python -m build
|
|
220
|
-
|
|
221
|
-
# 2) Validate metadata/rendering
|
|
222
|
-
uvx twine check dist/*
|
|
223
|
-
|
|
224
|
-
# 3) (Optional) test publish first
|
|
225
|
-
uvx twine upload --repository testpypi dist/*
|
|
201
|
+
## License
|
|
226
202
|
|
|
227
|
-
|
|
228
|
-
uvx twine upload dist/*
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
If using an API token:
|
|
232
|
-
|
|
233
|
-
```bash
|
|
234
|
-
export TWINE_USERNAME=__token__
|
|
235
|
-
export TWINE_PASSWORD=<your-pypi-token>
|
|
236
|
-
```
|
|
203
|
+
MIT. See `LICENSE`.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
scholarinboxcli/__init__.py,sha256=YvuYzWnKtqBb-IqG8HAu-nhIYAsgj9Vmc_b9o7vO-js,22
|
|
2
|
+
scholarinboxcli/cli.py,sha256=BPXTPhBVO6mDoihXfDIp6zmbTqIcMJ8-VqKiF616DrI,1015
|
|
3
|
+
scholarinboxcli/config.py,sha256=cxp1RzNwzT6Iu225EPvs8NhH2YTTMg9fQBjaYIRVDoc,1545
|
|
4
|
+
scholarinboxcli/api/client.py,sha256=pEUMcoTauEmDdB7weV5m82-yyjUpdcvbRe8oY9Rj8Qo,15142
|
|
5
|
+
scholarinboxcli/api/endpoints.py,sha256=lwSk3PAOITGurTliP9Ko8KSP-DFR8WqKrRxC760rpPc,1515
|
|
6
|
+
scholarinboxcli/commands/__init__.py,sha256=aOPGu7M21finmICNxDSxNBpe1C3Vw2Iimh1UBESfekU,42
|
|
7
|
+
scholarinboxcli/commands/auth.py,sha256=6iwFsuAqiwiRTJbaJFgEt4JXRo1HVrLEF3WSsMo3GHA,1024
|
|
8
|
+
scholarinboxcli/commands/bookmarks.py,sha256=UbR99VrrCyCdu1EhyHIgwn3sPOYKfRc4jF4gUx1fU6w,1598
|
|
9
|
+
scholarinboxcli/commands/collections.py,sha256=pBZFXi_e3Dto-4E2GS6pgIrcVMzWpWaGLNTLB5My5sQ,5634
|
|
10
|
+
scholarinboxcli/commands/common.py,sha256=5EcH0RDuX8DKzyB6lrPqGEok5TKx8SSV37mXczD9dqI,1654
|
|
11
|
+
scholarinboxcli/commands/conferences.py,sha256=e1QjX9R9WZhkSkPXKgoxuRPbl612qEVbsjX90zEgKSw,1183
|
|
12
|
+
scholarinboxcli/commands/papers.py,sha256=mdb3-_iecy_pjavPL_K0cXfVnDsQMU93SbD8T3hJY10,3968
|
|
13
|
+
scholarinboxcli/formatters/domain_tables.py,sha256=9RZcq-k9YycO64FvQAblE1dzYmytSj7NBasBEZeDW9I,4702
|
|
14
|
+
scholarinboxcli/formatters/json_fmt.py,sha256=Ntcp4EqHugCXg79RIF62c7QHa-lexptLDDTT3IEP65U,197
|
|
15
|
+
scholarinboxcli/formatters/table.py,sha256=G0ehvuGgo77XX5kw_9u5DEf8oUTIR-fOJ5rhYQvMEkc,4587
|
|
16
|
+
scholarinboxcli/services/__init__.py,sha256=i-8EAvmxCDyYEjmfILYRcUHUh7ryY5hFS4PZARScFXo,56
|
|
17
|
+
scholarinboxcli/services/collections.py,sha256=rz9rqgf-nfzBl-scBhweA-22--Z70qKEyBhW84os4bY,4353
|
|
18
|
+
scholarinboxcli/services/paper_sort.py,sha256=HYoIHry14QIUsfd9D9kZvy7phVhvCnwEjKHv6zO-0IU,1809
|
|
19
|
+
scholarinboxcli-0.1.2.dist-info/METADATA,sha256=oqmUh6jnAapWYtUA13efz3k4r1Q4kQutPP_nGEOzS00,5328
|
|
20
|
+
scholarinboxcli-0.1.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
21
|
+
scholarinboxcli-0.1.2.dist-info/entry_points.txt,sha256=iescoEMF_CPwSNSmvlzNDl5pT2VpBL9_1bIq_FFIAKc,60
|
|
22
|
+
scholarinboxcli-0.1.2.dist-info/licenses/LICENSE,sha256=sP1DPhQGvTFx1mH4JhNuczMMbApQgFvhBcVjZKM79gU,1068
|
|
23
|
+
scholarinboxcli-0.1.2.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Marek Suppa
|
|
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.
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
scholarinboxcli/__init__.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
|
|
2
|
-
scholarinboxcli/cli.py,sha256=bugBfT66k5EuVPashcG6Pz0IRM0KtRz_c6lNOalm4pk,19242
|
|
3
|
-
scholarinboxcli/config.py,sha256=cxp1RzNwzT6Iu225EPvs8NhH2YTTMg9fQBjaYIRVDoc,1545
|
|
4
|
-
scholarinboxcli/api/client.py,sha256=TE8pIRXh7pHhQGto9CvRpPPT61SJp9SzNhRnQ-2U4M4,13723
|
|
5
|
-
scholarinboxcli/formatters/json_fmt.py,sha256=Ntcp4EqHugCXg79RIF62c7QHa-lexptLDDTT3IEP65U,197
|
|
6
|
-
scholarinboxcli/formatters/table.py,sha256=GnzpmSJ7M_yq-R-c8no8SE9vXbycvWWPUu6hV4tcJAA,2133
|
|
7
|
-
scholarinboxcli-0.1.0.dist-info/METADATA,sha256=JZISdlP49Ezyh-UoLv2FlJIrliHUvZrnj0IvFNWKCMw,6062
|
|
8
|
-
scholarinboxcli-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
9
|
-
scholarinboxcli-0.1.0.dist-info/entry_points.txt,sha256=iescoEMF_CPwSNSmvlzNDl5pT2VpBL9_1bIq_FFIAKc,60
|
|
10
|
-
scholarinboxcli-0.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|