anyfreebook-cli 0.0.0-stage → 0.1.1

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Michael Banigo
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.
package/README.md CHANGED
@@ -1,3 +1,459 @@
1
- # Temporary Holding Version
1
+ # anyfreebook-cli
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **Search seven open-access book sources and download only what is verifiably public domain — from your terminal.**
4
+
5
+ ```bash
6
+ anyfreebook download "pride and prejudice" --format epub
7
+ ```
8
+
9
+ ---
10
+
11
+ ## Why this exists
12
+
13
+ Most "free book" APIs hand you a title and a link, and leave you to work out
14
+ whether the thing behind that link is actually free. Some are public domain.
15
+ Some are borrow-only loans you need an account for. Some are a catalogue
16
+ record for a book that is still under copyright.
17
+
18
+ This tool asks, per result, and tells you which one you have:
19
+
20
+ | tier | meaning | can you download it? |
21
+ | --- | --- | --- |
22
+ | `download` | A free, legally readable copy of this title. | **Yes**, when the API offers a file. A few sources are free to read but expose no file to fetch — the CLI says so instead of downloading nothing quietly. |
23
+ | `borrow` | A real copy, but borrow-only: a free Internet Archive account and a loan are required. | No. The CLI says so and points you at the loan page. |
24
+ | `unverified` | A catalogue record or a search page. No confirmed free copy of this exact title. | No. The CLI says so. |
25
+
26
+ The tier is never a guess. For the Internet Archive it comes from the item's
27
+ own metadata (`access-restricted-item` and `lending_identifier`); for Project
28
+ Gutenberg we only claim a download when the title and the author match a real
29
+ ebook and its id resolves to a real file.
30
+
31
+ The CLI will not download anything that is not `download`, and it never skips a
32
+ result silently — it tells you why.
33
+
34
+ ---
35
+
36
+ ## Install
37
+
38
+ The package is not on npm yet. Run it from a clone:
39
+
40
+ ```bash
41
+ git clone https://github.com/michaelbanigo575-bot/anyfreebook-cli.git
42
+ cd anyfreebook-cli
43
+ npm install
44
+ npm link # puts `anyfreebook` on your PATH
45
+ ```
46
+
47
+ Or run it without installing:
48
+
49
+ ```bash
50
+ node bin/anyfreebook.js search "python"
51
+ ```
52
+
53
+ Once it is published to npm this becomes:
54
+
55
+ ```bash
56
+ npm install -g anyfreebook-cli
57
+ npx anyfreebook-cli search "python"
58
+ ```
59
+
60
+ Requires Node 18 or newer. No runtime dependencies.
61
+
62
+ ---
63
+
64
+ ## What it looks like
65
+
66
+ ```console
67
+ $ anyfreebook search "pride and prejudice" --source gutenberg --limit 5
68
+
69
+ Query pride and prejudice
70
+ Page 1 · limit 5 · 4 returned · 6 total matches
71
+ Sources Project Gutenberg 6
72
+
73
+ TITLE AUTHOR SOURCE TIER FORMATS
74
+ ----------------------------------------------------------------------------------------------------
75
+ Pride and Prejudice Jane Austen Project Gutenberg download epub, txt, html
76
+ Pride and Prejudice Austen, Jane, 1775-1817 Project Gutenberg download epub, txt, html
77
+ Pride and Prejudice, a play founded Steele, Mrs. MacKaye, Jane A Project Gutenberg download epub, txt, html
78
+ on Jane Austen's novel
79
+ Pride and Prejudice Jane Austen, R. W. Chapman Project Gutenberg download epub, txt, html
80
+
81
+ More results available — use --page 2 for the next page.
82
+
83
+ download = verified public-domain full text · borrow = lending-restricted (needs a
84
+ free Internet Archive account + loan) · unverified = no confirmed free copy of this
85
+ exact title
86
+ ```
87
+
88
+ And a download:
89
+
90
+ ```console
91
+ $ anyfreebook download "pride and prejudice" --source gutenberg --format epub --limit 2
92
+
93
+ ↓ pride-and-prejudice-pg-1342.epub 23.7 MB
94
+ ✓ Pride and Prejudice — ./books/pride-and-prejudice-pg-1342.epub (23.7 MB)
95
+
96
+ Summary: downloaded 1 · skipped 0 · failed 0 · refused 0
97
+ ```
98
+
99
+ When a source did not answer, the CLI says so rather than letting you think
100
+ you saw everything:
101
+
102
+ ```console
103
+ $ anyfreebook search "python" --limit 5
104
+
105
+ ! 1 source did not respond: Open Library. Results below are partial.
106
+
107
+ Query python
108
+ ...
109
+ ```
110
+
111
+ ---
112
+
113
+ ## Quick start
114
+
115
+ ```bash
116
+ # find books
117
+ anyfreebook search "python"
118
+
119
+ # only what is genuinely free to download
120
+ anyfreebook search "python" --free-only
121
+
122
+ # only one source
123
+ anyfreebook search "pride and prejudice" --source gutenberg
124
+
125
+ # only results that offer a specific format
126
+ anyfreebook search "python" --format epub
127
+
128
+ # download to disk (default folder: ./books)
129
+ anyfreebook download "pride and prejudice" --format epub --out ./books
130
+
131
+ # see what would happen, write nothing
132
+ anyfreebook download "pride and prejudice" --dry-run
133
+
134
+ # the careful answer for one title
135
+ anyfreebook resolve "Pride and Prejudice" --author "Jane Austen"
136
+
137
+ # machine-readable output
138
+ anyfreebook search "python" --json | jq '.results[] | select(.tier=="download") | .title'
139
+ ```
140
+
141
+ ---
142
+
143
+ ## Commands
144
+
145
+ ### `anyfreebook search <query>`
146
+
147
+ | Option | Default | Meaning |
148
+ | --- | --- | --- |
149
+ | `--limit <n>` | 10 | Results per page (max 50). |
150
+ | `--page <n>` | 1 | Page number. |
151
+ | `--source <name>` | `all` | `all`, `gutenberg`, `openlibrary`, `archive`, `googlebooks`, `pubmed`, `doaj`, `librivox`. |
152
+ | `--format <fmt>` | — | Keep only results offering `epub`, `pdf`, `txt` or `html`. |
153
+ | `--free-only` | off | Keep only `tier === "download"` results. |
154
+ | `--json` | off | Print the raw JSON response to stdout and nothing else. |
155
+
156
+ ### `anyfreebook download <query>`
157
+
158
+ | Option | Default | Meaning |
159
+ | --- | --- | --- |
160
+ | `--author <name>` | — | Resolve this exact title+author instead of searching. Recommended. |
161
+ | `--limit <n>` | 5 | Results to consider. |
162
+ | `--source <name>` | `all` | Same values as `search`. |
163
+ | `--format <fmt>` | best available | `epub` > `pdf` > `txt` > `html`. |
164
+ | `--out <dir>` | `./books` | Destination folder. |
165
+ | `--force` | off | Overwrite a file that already exists. |
166
+ | `--dry-run` | off | Show what would be downloaded; write nothing. |
167
+
168
+ Downloads are **always** restricted to `tier === "download"`. A `borrow` or
169
+ `unverified` result is reported as refused, with the reason — it is never
170
+ skipped quietly.
171
+
172
+ A result is only downloaded if its **own title** is the title you asked for.
173
+ The search fans out to sources that match free text, so "Pride and Prejudice"
174
+ also finds real academic papers about prejudice, and one of those can outrank
175
+ the novel and carry a perfectly good PDF. Those are reported and skipped, not
176
+ downloaded as if they were the book.
177
+
178
+ Pass `--author` when you know it. That resolves one exact title against the
179
+ real record instead of searching, which is both faster and unambiguous:
180
+
181
+ ```bash
182
+ anyfreebook download "Pride and Prejudice" --author "Jane Austen" --format epub
183
+ ```
184
+
185
+ File names are `<title-slug>-<source-id>.<ext>`, so two different books with
186
+ the same title do not overwrite each other.
187
+
188
+ ### `anyfreebook resolve <title> [--author <name>]`
189
+
190
+ The authoritative answer for a single title. It checks the title and the author
191
+ against the real record, so a book by one Austen never resolves to a different
192
+ book that merely shares the surname — and a request for one title never comes
193
+ back with a different book attached.
194
+
195
+ ```console
196
+ $ anyfreebook resolve "Pride and Prejudice" --author "Jane Austen"
197
+
198
+ Pride and Prejudice
199
+ Author Jane Austen
200
+ Tier download
201
+ Reason Verified public-domain full text on Project Gutenberg.
202
+ License Public domain
203
+ Source https://www.gutenberg.org/ebooks/1342 (gutenberg)
204
+
205
+ Files (3)
206
+ EPUB https://www.gutenberg.org/ebooks/1342.epub3.images
207
+ Plain text https://www.gutenberg.org/ebooks/1342.txt.utf-8
208
+ HTML https://www.gutenberg.org/ebooks/1342.html.images
209
+ ```
210
+
211
+ ### `anyfreebook health`
212
+
213
+ ```console
214
+ $ anyfreebook health
215
+
216
+ Status ok
217
+ API ver v1
218
+ Uptime 3h 12m
219
+ Base URL https://anyfreebook.com/api/v1
220
+ ```
221
+
222
+ ### Global options
223
+
224
+ | Option | Environment variable | Default |
225
+ | --- | --- | --- |
226
+ | `--api-base <url>` | `ANYFREEBOOK_API_BASE` | `https://anyfreebook.com/api/v1` |
227
+ | `--api-key <key>` | `ANYFREEBOOK_API_KEY` | none — the anonymous tier works without one |
228
+ | `--timeout <ms>` | — | 20000 |
229
+ | `--json` | — | off |
230
+ | `--no-color` | `NO_COLOR` is honoured too | off |
231
+ | `--help`, `--version` | — | |
232
+
233
+ ### Exit codes
234
+
235
+ | Code | Meaning |
236
+ | --- | --- |
237
+ | `0` | Success. |
238
+ | `1` | Request or usage error. |
239
+ | `2` | Rate limited (HTTP 429). The message says how long to wait. |
240
+
241
+ ---
242
+
243
+ ## Python SDK
244
+
245
+ The same API from Python, with **no dependencies** — standard library only.
246
+
247
+ ```bash
248
+ pip install anyfreebook-api
249
+ ```
250
+
251
+ ```python
252
+ from anyfreebook_api import Client
253
+
254
+ client = Client() # base_url and api_key read from the environment
255
+ result = client.search("python", limit=5)
256
+
257
+ for book in result.results:
258
+ if book.tier == "download":
259
+ print(book.title, "->", book.files[0].url)
260
+
261
+ resolved = client.resolve("Pride and Prejudice", author="Jane Austen")
262
+ print(resolved.tier, resolved.tier_reason)
263
+ ```
264
+
265
+ `Client(base_url=..., api_key=..., timeout=...)` — every argument is optional.
266
+ A `429` raises `RateLimitError` with `retry_after`; other failures raise
267
+ `ApiError`; a network failure raises `NetworkError`.
268
+
269
+ If `result.partial` is `True`, some sources did not answer — check
270
+ `result.failed_sources` before treating the list as complete.
271
+
272
+ ---
273
+
274
+ ## Use it in your app
275
+
276
+ ```js
277
+ const res = await fetch('https://anyfreebook.com/api/v1/search?q=python&limit=5');
278
+ const data = await res.json();
279
+
280
+ for (const book of data.results) {
281
+ if (book.tier === 'download') {
282
+ console.log(book.title, book.files[0]?.url);
283
+ }
284
+ }
285
+ ```
286
+
287
+ ```python
288
+ import json, urllib.request
289
+
290
+ url = "https://anyfreebook.com/api/v1/search?q=python&limit=5"
291
+ with urllib.request.urlopen(url) as r:
292
+ data = json.load(r)
293
+
294
+ for book in data["results"]:
295
+ if book["tier"] == "download":
296
+ print(book["title"], book["files"][0]["url"])
297
+ ```
298
+
299
+ ---
300
+
301
+ ## API reference
302
+
303
+ Base URL: `https://anyfreebook.com/api/v1`
304
+
305
+ ### `GET /search`
306
+
307
+ | Parameter | Type | Default | Notes |
308
+ | --- | --- | --- | --- |
309
+ | `q` | string | — | **Required.** Max 200 characters. |
310
+ | `page` | integer | `1` | |
311
+ | `source` | string | `all` | `all`, `local`, `gutenberg`, `openlibrary`, `archive`, `googlebooks`, `pubmed`, `doaj`, `librivox`. |
312
+ | `limit` | integer | `20` | 1–50. |
313
+
314
+ ```json
315
+ {
316
+ "query": "python",
317
+ "page": 1,
318
+ "limit": 20,
319
+ "total": 22537,
320
+ "count": 20,
321
+ "sources": { "Project Gutenberg": 120, "Open Library": 22417 },
322
+ "failedSources": [],
323
+ "partial": false,
324
+ "results": [
325
+ {
326
+ "id": "pg-39560",
327
+ "title": "Fairy Circles",
328
+ "author": "Villamaria",
329
+ "year": null,
330
+ "source": "Project Gutenberg",
331
+ "sourceType": "gutenberg",
332
+ "sourceUrl": "https://www.gutenberg.org/ebooks/39560",
333
+ "tier": "download",
334
+ "tierReason": "Verified public-domain full text on Project Gutenberg.",
335
+ "license": "Public domain (Project Gutenberg)",
336
+ "files": [
337
+ { "format": "epub", "url": "https://www.gutenberg.org/ebooks/39560.epub3.images", "bytes": null, "label": "EPUB" }
338
+ ],
339
+ "links": [
340
+ { "label": "Read on Project Gutenberg", "url": "https://www.gutenberg.org/ebooks/39560", "source": "Project Gutenberg" }
341
+ ],
342
+ "url": "https://anyfreebook.com/book/fairy-circles-pg-39560"
343
+ }
344
+ ],
345
+ "meta": { "apiVersion": "v1", "generatedAt": "2026-10-23T23:30:00.000Z" }
346
+ }
347
+ ```
348
+
349
+ `failedSources` lists the sources that did not answer, and `partial` is `true`
350
+ when it is non-empty. A partial result set is **not** an empty result set.
351
+
352
+ `files` holds direct file URLs, and only when the file genuinely exists.
353
+ Project Gutenberg always offers EPUB, plain text and HTML for a valid ebook
354
+ id; a PDF is listed only when the source reported one.
355
+
356
+ ### `GET /resolve`
357
+
358
+ | Parameter | Type | Required | Notes |
359
+ | --- | --- | --- | --- |
360
+ | `title` | string | **yes** | Max 300 characters. |
361
+ | `author` | string | no | Used to confirm the match. |
362
+
363
+ ```json
364
+ {
365
+ "title": "Pride and Prejudice",
366
+ "author": "Jane Austen",
367
+ "tier": "download",
368
+ "tierReason": "Verified public-domain full text on Project Gutenberg.",
369
+ "sourceType": "gutenberg",
370
+ "sourceUrl": "https://www.gutenberg.org/ebooks/1342",
371
+ "license": "Public domain",
372
+ "files": [
373
+ { "format": "epub", "url": "https://www.gutenberg.org/ebooks/1342.epub3.images", "bytes": null, "label": "EPUB" }
374
+ ],
375
+ "links": [
376
+ { "label": "Read on Project Gutenberg", "url": "https://www.gutenberg.org/ebooks/1342", "source": "Project Gutenberg" }
377
+ ],
378
+ "meta": { "apiVersion": "v1", "generatedAt": "2026-10-23T23:30:00.000Z" }
379
+ }
380
+ ```
381
+
382
+ ### `GET /health`
383
+
384
+ ```json
385
+ { "status": "ok", "apiVersion": "v1", "uptimeSeconds": 1234 }
386
+ ```
387
+
388
+ ### Authentication
389
+
390
+ Optional. The anonymous tier works with no setup — send a key only if you
391
+ need a bigger quota.
392
+
393
+ ```bash
394
+ curl -H "Authorization: Bearer afb_..." "https://anyfreebook.com/api/v1/search?q=python"
395
+ ```
396
+
397
+ | Tier | Requests / minute | Requests / day |
398
+ | --- | --- | --- |
399
+ | anonymous (no key) | 30 | 200 |
400
+ | `free` | 60 | 2,000 |
401
+ | `partner` | 300 | 50,000 |
402
+
403
+ A key that cannot be verified gets `401`. It is never silently downgraded to
404
+ the anonymous tier.
405
+
406
+ ### Errors
407
+
408
+ | Status | Meaning |
409
+ | --- | --- |
410
+ | `400` | Missing or invalid parameter. |
411
+ | `401` | The presented API key could not be verified. |
412
+ | `429` | Rate limit exceeded. The `Retry-After` header says how long to wait. |
413
+ | `500` | Something broke on our side. |
414
+
415
+ ### CORS
416
+
417
+ Closed by default. A command-line client or a server-side SDK does not need
418
+ CORS at all. Browser clients need their origin added to the server's
419
+ allowlist.
420
+
421
+ ---
422
+
423
+ ## Sources
424
+
425
+ Project Gutenberg · Open Library · Internet Archive · Google Books ·
426
+ PubMed Central · DOAJ · LibriVox
427
+
428
+ Google Books records are catalogue entries, not free copies, and are reported
429
+ as `unverified` for exactly that reason.
430
+
431
+ ---
432
+
433
+ ## Contributing
434
+
435
+ ```bash
436
+ git clone https://github.com/michaelbanigo575-bot/anyfreebook-cli.git
437
+ cd anyfreebook-cli
438
+ npm install
439
+ npm test
440
+ ```
441
+
442
+ The test suite runs entirely against a local mock server — no network access,
443
+ no API key needed. The Python SDK has its own suite:
444
+
445
+ ```bash
446
+ cd python
447
+ python -m pytest tests/ -q
448
+ ```
449
+
450
+ Issues and pull requests are welcome: <https://github.com/michaelbanigo575-bot/anyfreebook-cli/issues>
451
+
452
+ ## License
453
+
454
+ MIT — see [LICENSE](./LICENSE).
455
+
456
+ ---
457
+
458
+ The API behind this tool is free and needs no key to start:
459
+ <https://anyfreebook.com/developers>
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * anyfreebook — terminal client for the ANYFREEBOOK public API (v1).
4
+ */
5
+
6
+ import { main } from '../src/cli.js';
7
+
8
+ main(process.argv.slice(2));
package/package.json CHANGED
@@ -1,6 +1,51 @@
1
1
  {
2
2
  "name": "anyfreebook-cli",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "Search 7 open-access book sources and download only what is verifiably public domain.",
5
+ "type": "module",
6
+ "bin": {
7
+ "anyfreebook": "bin/anyfreebook.js"
8
+ },
9
+ "engines": {
10
+ "node": ">=18"
11
+ },
12
+ "license": "MIT",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/michaelbanigo575-bot/anyfreebook-cli.git"
16
+ },
17
+ "bugs": {
18
+ "url": "https://github.com/michaelbanigo575-bot/anyfreebook-cli/issues"
19
+ },
20
+ "homepage": "https://github.com/michaelbanigo575-bot/anyfreebook-cli#readme",
21
+ "keywords": [
22
+ "anyfreebook",
23
+ "books",
24
+ "ebook",
25
+ "open-access",
26
+ "public-domain",
27
+ "project-gutenberg",
28
+ "open-library",
29
+ "internet-archive",
30
+ "librivox",
31
+ "doaj",
32
+ "pubmed",
33
+ "cli",
34
+ "search",
35
+ "download",
36
+ "free"
37
+ ],
38
+ "files": [
39
+ "bin",
40
+ "src",
41
+ "README.md",
42
+ "LICENSE"
43
+ ],
44
+ "scripts": {
45
+ "test": "node --test \"test/*.test.js\"",
46
+ "help": "node bin/anyfreebook.js --help"
47
+ },
48
+ "publishConfig": {
49
+ "access": "public"
50
+ }
51
+ }