nusus 0.3.0 → 0.4.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Nusus
2
2
 
3
- TypeScript SDK for searching and citing classical Islamic and Arabic texts through [Turath](https://app.turath.io/), a Shamela-style digital library. Use Nusus to retrieve source text, metadata, citations, locators, and direct links from Arabic heritage books for research tools and AI agents.
3
+ TypeScript SDK **and agent CLI** for searching and citing classical Islamic and Arabic texts through [Turath](https://app.turath.io/), a Shamela-style digital library. Use Nusus to retrieve source text, metadata, citations, locators, and direct links from Arabic heritage books for research tools and AI agents. The `nusus` binary ships in the same npm package as the SDK.
4
4
 
5
5
  ```bash
6
6
  npm install nusus
@@ -48,16 +48,49 @@ All requests support `AbortSignal`; failures use the exported `NususError` codes
48
48
 
49
49
  ## Agent CLI
50
50
 
51
- ```bash
52
- npx nusus "إنما الأعمال بالنيات" 3
53
- npx nusus --book-id 147927 "النية" 3
54
- npx nusus --page 147927 5
51
+ The package installs a `nusus` binary (`scripts/search.mjs`) that wraps the SDK as explicit research tools. Prefer the CLI from agents; use the SDK from application code.
55
52
 
56
- # Bun
57
- bunx nusus "إنما الأعمال بالنيات" 3
53
+ ```bash
54
+ npm install -g nusus # or: npx nusus
55
+ nusus --help
56
+ nusus --version
57
+
58
+ # Discover (offline bundled catalog)
59
+ nusus find-books "الأربعون النووية" --limit 5
60
+ nusus find-books --author-id 44 --limit 10
61
+ nusus find-authors "النووي" --limit 5
62
+ nusus list-categories
63
+ nusus catalog
64
+
65
+ # Search / retrieve (live Turath)
66
+ nusus search "إنما الأعمال بالنيات" --book-id 147927
67
+ nusus retrieve "النية" --max-passages 3 --max-chars 2000
68
+
69
+ # Page / metadata (live)
70
+ nusus get-page --book-id 147927 --page-id 5
71
+ nusus get-context --book-id 147927 --page-id 5 --pages-before 1 --pages-after 1
72
+ nusus get-book 147927
73
+ nusus get-author 44
58
74
  ```
59
75
 
60
- The CLI emits JSON Lines with passage text, source metadata, citations, and direct Turath URLs. It also supports `--madhhab hanafi|maliki|shafii|hanbali` and `--books "title one,title two"`.
76
+ Default stdout is **JSONL** (one object per line, camelCase, every line has `type`). Use `--format text` for compact human lines. Diagnostics are JSON on **stderr** only. Unknown or command-inapplicable flags are rejected.
77
+
78
+ | Record `type` | Commands |
79
+ | --- | --- |
80
+ | `meta` | First line of `find-books`, `find-authors`, `search`, `retrieve` |
81
+ | `book` / `author` / `category` / `catalog` | Discovery + `get-book` / `get-author` |
82
+ | `passage` | `search`, `retrieve`, `get-page`, `get-context` |
83
+
84
+ | Exit | Meaning |
85
+ | --- | --- |
86
+ | 0 | Success (including zero hits; find/search/retrieve emit a `meta` line; offline finders report `returned`, live search/retrieve report `totalMatches`) |
87
+ | 1 | Usage / invalid argument |
88
+ | 2 | Not found |
89
+ | 3 | Rate limit / HTTP / invalid response / timeout (`ABORTED`) / internal |
90
+
91
+ `page-id` is Turath’s internal page (`location.internalPage`), not the printed page. `search`/`retrieve` accept at most one `--book-id`, one `--author-id`, and one `--category-id` (filters may be combined). Offline `find-books` accepts repeated `--author-id`/`--category-id` and may omit the title query when those filters are set. `--timeout 0` disables the request timeout (max `600000`). There is no madhhab ranking or multi-book fanout in the CLI.
92
+
93
+ Repo layout and module boundaries: [`docs/CODEBASE_MAP.md`](docs/CODEBASE_MAP.md). Agent skill copy: [`docs/turath-research-SKILL.md`](docs/turath-research-SKILL.md).
61
94
 
62
95
  ## Known limitations
63
96
 
@@ -71,9 +104,9 @@ Browser support is not claimed because the checked API responses do not advertis
71
104
 
72
105
  ```bash
73
106
  bun install
107
+ bun run build # CLI bin loads dist/; also auto-built by CLI tests if missing
74
108
  bun test
75
109
  bun run typecheck
76
- bun run build
77
110
 
78
111
  # optional live contract (also scheduled via GitHub Actions)
79
112
  bun run test:live
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { NususError } from "./errors.js";
2
2
  export type { NususErrorCode } from "./errors.js";
3
- export type { Author, AuthorSummary, Book, BookSummary, CatalogMetadata, Category, CategorySummary, Passage, PassageProvenance, RetrievedContext, RetrieveScope, SearchPage, SourceLocation, SourceLocator, TurathId, } from "./models.js";
3
+ export type { Author, AuthorSummary, Book, BookSummary, BookTocEntry, CatalogMetadata, Category, CategorySummary, Passage, PassageProvenance, RetrievedContext, RetrieveScope, SearchPage, SourceLocation, SourceLocator, TurathId, } from "./models.js";
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,YAAY,EACV,MAAM,EACN,aAAa,EACb,IAAI,EACJ,WAAW,EACX,eAAe,EACf,QAAQ,EACR,eAAe,EACf,OAAO,EACP,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,UAAU,EACV,cAAc,EACd,aAAa,EACb,QAAQ,GACT,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,YAAY,EACV,MAAM,EACN,aAAa,EACb,IAAI,EACJ,WAAW,EACX,YAAY,EACZ,eAAe,EACf,QAAQ,EACR,eAAe,EACf,OAAO,EACP,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,UAAU,EACV,cAAc,EACd,aAAa,EACb,QAAQ,GACT,MAAM,aAAa,CAAC"}
package/dist/models.d.ts CHANGED
@@ -16,6 +16,11 @@ export type BookSummary = {
16
16
  id: string;
17
17
  title: string;
18
18
  };
19
+ export type BookTocEntry = {
20
+ title: string;
21
+ level?: number;
22
+ page?: number;
23
+ };
19
24
  export type Author = AuthorSummary & {
20
25
  provider: "turath";
21
26
  id: string;
@@ -29,6 +34,10 @@ export type Book = BookSummary & {
29
34
  category?: CategorySummary;
30
35
  description?: string;
31
36
  hasPdf?: boolean;
37
+ /** Normalized TOC from Turath book indexes when present. */
38
+ toc?: BookTocEntry[];
39
+ /** Volume labels from Turath book indexes when present. */
40
+ volumes?: string[];
32
41
  raw?: unknown;
33
42
  };
34
43
  export type SourceLocation = {
@@ -1 +1 @@
1
- {"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;AAEvC,MAAM,MAAM,aAAa,GAAG;IAC1B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,eAAe,GAAG;IACvC,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,MAAM,GAAG,aAAa,GAAG;IACnC,QAAQ,EAAE,QAAQ,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,IAAI,GAAG,WAAW,GAAG;IAC/B,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,CAAC,EAAE,QAAQ,EAAE,CAAC;IACrB,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;IACvB,WAAW,CAAC,EAAE,QAAQ,EAAE,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,QAAQ,GAAG,cAAc,CAAC;IACvC,YAAY,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAChD,YAAY,EAAE,MAAM,GAAG,YAAY,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG;IACpB,QAAQ,EAAE,QAAQ,CAAC;IACnB,IAAI,EAAE,WAAW,CAAC;IAClB,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,QAAQ,EAAE,cAAc,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B,GAAG,CAAC,EAAE,OAAO,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,kEAAkE;IAClE,aAAa,EAAE,MAAM,CAAC;IACtB,sEAAsE;IACtE,eAAe,EAAE,MAAM,CAAC;IACxB,2GAA2G;IAC3G,uBAAuB,EAAE,MAAM,CAAC;CACjC,CAAC"}
1
+ {"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import type { Author, Book, CatalogMetadata, Category, TurathId } from "../models.js";
2
2
  export type FindBooksOptions = {
3
+ authorIds?: TurathId[];
3
4
  categoryIds?: TurathId[];
4
5
  limit?: number;
5
6
  };
@@ -7,7 +8,7 @@ export type FindAuthorsOptions = {
7
8
  limit?: number;
8
9
  };
9
10
  export declare const getCatalogMetadata: () => CatalogMetadata;
10
- export declare const findCatalogBooks: (query: string, options?: FindBooksOptions) => Book[];
11
+ export declare const findCatalogBooks: (query?: string, options?: FindBooksOptions) => Book[];
11
12
  export declare const findCatalogAuthors: (query: string, options?: FindAuthorsOptions) => Author[];
12
13
  export declare const listCatalogCategories: () => Category[];
13
14
  export declare const listCatalogAuthorIds: () => string[];
@@ -1 +1 @@
1
- {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../src/turath/catalog.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAItF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,CAAC,EAAE,QAAQ,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAyCF,eAAO,MAAM,kBAAkB,QAAO,eAarC,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,OAAO,MAAM,EAAE,UAAS,gBAAqB,KAAG,IAAI,EA6BpF,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,EAAE,UAAS,kBAAuB,KAAG,MAAM,EAqB1F,CAAC;AAEF,eAAO,MAAM,qBAAqB,QAAO,QAAQ,EAShD,CAAC;AAEF,eAAO,MAAM,oBAAoB,QAAO,MAAM,EACU,CAAC"}
1
+ {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../src/turath/catalog.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAItF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;IACvB,WAAW,CAAC,EAAE,QAAQ,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAyCF,eAAO,MAAM,kBAAkB,QAAO,eAarC,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,cAAU,EAAE,UAAS,gBAAqB,KAAG,IAAI,EAkCjF,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,OAAO,MAAM,EAAE,UAAS,kBAAuB,KAAG,MAAM,EAqB1F,CAAC;AAEF,eAAO,MAAM,qBAAqB,QAAO,QAAQ,EAShD,CAAC;AAEF,eAAO,MAAM,oBAAoB,QAAO,MAAM,EACU,CAAC"}
@@ -54,22 +54,27 @@ export const getCatalogMetadata = () => {
54
54
  unresolvedAuthorIdCount: uniqueAuthorIds.size - authorNameCount,
55
55
  };
56
56
  };
57
- export const findCatalogBooks = (query, options = {}) => {
57
+ export const findCatalogBooks = (query = "", options = {}) => {
58
58
  const needle = normalize(query);
59
- if (!needle)
60
- throw new NususError("INVALID_ARGUMENT", "query must not be empty");
61
- const limit = positiveLimit(options.limit, 20);
59
+ const authors = options.authorIds?.map((value) => positiveId(value, "author id"));
62
60
  const categories = options.categoryIds?.map((value) => positiveId(value, "category id"));
61
+ const authorSet = authors?.length ? new Set(authors) : undefined;
63
62
  const categorySet = categories?.length ? new Set(categories) : undefined;
64
- const words = needle.split(/\s+/);
63
+ if (!needle && !authorSet && !categorySet) {
64
+ throw new NususError("INVALID_ARGUMENT", "query must not be empty unless authorIds or categoryIds are set");
65
+ }
66
+ const limit = positiveLimit(options.limit, 20);
67
+ const words = needle ? needle.split(/\s+/) : [];
65
68
  return CATALOG_BOOKS
66
69
  .flatMap(([bookId, title, authorId, categoryId]) => {
70
+ const authorKey = String(authorId);
71
+ if (authorSet && !authorSet.has(authorKey))
72
+ return [];
67
73
  if (categorySet && !categorySet.has(String(categoryId)))
68
74
  return [];
69
- const score = scoreMatch(normalize(title), needle, words);
75
+ const score = needle ? scoreMatch(normalize(title), needle, words) : 0;
70
76
  if (score < 0)
71
77
  return [];
72
- const authorKey = String(authorId);
73
78
  const authorName = authorNames.get(authorKey);
74
79
  return [{
75
80
  score,
@@ -1 +1 @@
1
- {"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../src/turath/catalog.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../src/turath/catalog.ts"],"names":[],"mappings":"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"}
@@ -24,7 +24,7 @@ export type RetrieveOptions = RequestOptions & {
24
24
  };
25
25
  export type TurathClientOptions = TransportOptions;
26
26
  export declare const createTurathClient: (options?: TurathClientOptions) => {
27
- findBooks: (query: string, options?: import("./catalog.js").FindBooksOptions) => Book[];
27
+ findBooks: (query?: string, options?: import("./catalog.js").FindBooksOptions) => Book[];
28
28
  findAuthors: (query: string, options?: import("./catalog.js").FindAuthorsOptions) => import("../models.js").Author[];
29
29
  listCategories: () => import("../models.js").Category[];
30
30
  getCatalogMetadata: () => import("../models.js").CatalogMetadata;
@@ -1 +1 @@
1
- {"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../../src/turath/normalize.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAwB1D,eAAO,MAAM,eAAe,GAAI,KAAK,OAAO,KAAG,MAa9C,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,KAAK,OAAO,KAAG,IAe5C,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,KAAK,OAAO,EAAE,QAAQ,MAAM,KAAG,OAoB5D,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,KAAK,OAAO,KAAG,OAsBjD,CAAC"}
1
+ {"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../../src/turath/normalize.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,IAAI,EAAgB,OAAO,EAAE,MAAM,cAAc,CAAC;AA6CxE,eAAO,MAAM,eAAe,GAAI,KAAK,OAAO,KAAG,MAa9C,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,KAAK,OAAO,KAAG,IAiB5C,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,KAAK,OAAO,EAAE,QAAQ,MAAM,KAAG,OAoB5D,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,KAAK,OAAO,KAAG,OAsBjD,CAAC"}
@@ -1,6 +1,31 @@
1
1
  import { NususError } from "../errors.js";
2
2
  import { decoratePassage } from "./citations.js";
3
3
  const record = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4
+ const extractIndexes = (raw) => {
5
+ const indexes = raw.indexes;
6
+ if (!record(indexes))
7
+ return {};
8
+ const out = {};
9
+ if (Array.isArray(indexes.headings)) {
10
+ const toc = [];
11
+ for (const item of indexes.headings) {
12
+ if (!record(item) || typeof item.title !== "string")
13
+ continue;
14
+ const entry = { title: item.title };
15
+ if (typeof item.level === "number")
16
+ entry.level = item.level;
17
+ if (typeof item.page === "number")
18
+ entry.page = item.page;
19
+ toc.push(entry);
20
+ }
21
+ if (toc.length)
22
+ out.toc = toc;
23
+ }
24
+ if (Array.isArray(indexes.volumes) && indexes.volumes.every((value) => typeof value === "string")) {
25
+ out.volumes = indexes.volumes;
26
+ }
27
+ return out;
28
+ };
4
29
  const invalid = (message, cause) => {
5
30
  throw new NususError("INVALID_RESPONSE", message, { cause });
6
31
  };
@@ -39,6 +64,7 @@ export const normalizeBook = (raw) => {
39
64
  return invalid("Turath returned an invalid book");
40
65
  }
41
66
  const book = raw;
67
+ const indexes = extractIndexes(raw);
42
68
  return {
43
69
  provider: "turath",
44
70
  id: String(book.meta.id),
@@ -47,6 +73,7 @@ export const normalizeBook = (raw) => {
47
73
  ...(typeof book.meta.cat_id === "number" && { category: { id: String(book.meta.cat_id) } }),
48
74
  ...(typeof book.meta.info === "string" && { description: book.meta.info }),
49
75
  ...(Boolean(book.meta.pdf_links) && { hasPdf: true }),
76
+ ...indexes,
50
77
  raw,
51
78
  };
52
79
  };
@@ -1 +1 @@
1
- {"version":3,"file":"normalize.js","sourceRoot":"","sources":["../../src/turath/normalize.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"normalize.js","sourceRoot":"","sources":["../../src/turath/normalize.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nusus",
3
- "version": "0.3.0",
4
- "description": "TypeScript SDK for searching and citing classical Islamic and Arabic texts via Turath, a Shamela-style digital library",
3
+ "version": "0.4.1",
4
+ "description": "TypeScript SDK and agent CLI for searching and citing classical Islamic and Arabic texts via Turath",
5
5
  "keywords": [
6
6
  "turath",
7
7
  "shamela",
@@ -11,7 +11,9 @@
11
11
  "hadith",
12
12
  "fiqh",
13
13
  "typescript",
14
- "sdk"
14
+ "sdk",
15
+ "cli",
16
+ "jsonl"
15
17
  ],
16
18
  "repository": {
17
19
  "type": "git",
@@ -1,155 +1,642 @@
1
1
  #!/usr/bin/env node
2
- // Agent-facing Turath search CLI built on nusus.
3
- // Drop-in replacement for the old turath-sdk search.mjs.
4
- //
5
- // Usage:
6
- // node search.mjs "<Arabic query>" [max_results]
7
- // node search.mjs --madhhab hanafi|maliki|shafii|hanbali "<topic>" [max_results]
8
- // node search.mjs --books "بدائع الصنائع,الهداية" "<query>" [max_results]
9
- // node search.mjs --book-id 147927 "<query>" [max_results]
10
- // node search.mjs --page <book_id> <page_id> # fetch one page + context
11
- //
12
- // Output: JSON per line — book_id, title, author, volume, page_id, printed_page,
13
- // headings, citation, url, text (bounded), prev/next context.
2
+ // Agent-facing Turath CLI thin subcommand surface over the nusus SDK.
3
+ // Default stdout: JSONL. Diagnostics: stderr JSON. No ranking heuristics.
14
4
 
5
+ import { createRequire } from "node:module";
6
+ import { dirname, join } from "node:path";
7
+ import { fileURLToPath } from "node:url";
15
8
  import { createTurathClient } from "../dist/turath/index.js";
9
+ import { NususError } from "../dist/errors.js";
16
10
 
17
- const MADHHAB_BOOKS = {
18
- hanafi: ["رد المحتار ابن عابدين", "الهداية المرغيناني", "فتح القدير ابن الهمام", "بدائع الصنائع الكاساني", "المبسوط السرخسي"],
19
- maliki: ["مختصر خليل", "المدونة الكبرى سحنون", "التوضيح شرح مختصر ابن الحاجب خليل", "الشرح الكبير الدردير حاشية الدسوقي", "مواهب الجليل الحطاب"],
20
- shafii: ["منهاج الطالبين النووي", "تحفة المحتاج ابن حجر الهيتمي", "نهاية المحتاج الرملي", "مغني المحتاج الشربيني", "المجموع شرح المهذب النووي"],
21
- hanbali: ["منتهى الإرادات ابن النجار", "الإقناع الحجاوي", "كشاف القناع البهوتي", "شرح منتهى الإرادات البهوتي", "المغني ابن قدامة"],
22
- };
23
-
24
- const MADHHAB_LABELS = {
25
- hanafi: { name: "الحنفية", at: "عند الحنفية", fi: "في مذهب الحنفية" },
26
- maliki: { name: "المالكية", at: "عند المالكية", fi: "في المذهب المالكي" },
27
- shafii: { name: "الشافعية", at: "عند الشافعية", fi: "في مذهب الشافعية" },
28
- hanbali: { name: "الحنابلة", at: "عند الحنابلة", fi: "في المذهب الحنبلي" },
29
- };
30
-
31
- // === arg parsing ===
32
- const args = process.argv.slice(2);
33
- let madhhab = null;
34
- let books = null;
35
- let bookId = null;
36
- let pageMode = false;
37
- let query = null;
38
- let maxResults = 5;
39
-
40
- for (let i = 0; i < args.length; i++) {
41
- const a = args[i];
42
- if (a === "--madhhab") {
43
- madhhab = args[++i]?.toLowerCase();
44
- if (!MADHHAB_BOOKS[madhhab]) fail(`Invalid madhhab. Use: ${Object.keys(MADHHAB_BOOKS).join(", ")}`);
45
- } else if (a === "--books") books = args[++i]?.split(",").map((s) => s.trim()).filter(Boolean);
46
- else if (a === "--book-id") bookId = args[++i];
47
- else if (a === "--page") pageMode = true;
48
- else if (a === "--topic") query = args[++i];
49
- else if (!a.startsWith("--") && query === null && isNaN(Number(a))) query = a;
50
- }
51
- // last numeric non-flag arg = max results (or page args in --page mode)
52
- const numbers = args.filter((a) => !a.startsWith("--") && /^\d+$/.test(a));
53
- if (numbers.length && !pageMode) maxResults = parseInt(numbers[numbers.length - 1], 10);
54
-
55
- function fail(message) {
56
- console.error(message);
57
- process.exit(1);
58
- }
59
-
60
- const turath = createTurathClient({ timeout: 15_000 });
61
-
62
- const emit = (item) => console.log(JSON.stringify(item));
63
-
64
- const passageToJson = (p, extra = {}) => ({
65
- book_id: Number(p.book.id),
66
- title: p.book.title,
67
- author: p.author?.name,
68
- volume: p.location.volume,
69
- page_id: p.location.internalPage,
70
- printed_page: p.location.printedPage,
71
- headings: p.headings.join(" > "),
72
- citation: p.citation,
73
- url: p.url,
74
- ...extra,
11
+ const DEFAULT_TIMEOUT = 15_000;
12
+ const MAX_TIMEOUT = 600_000;
13
+ const MAX_FILTER_IDS = 50;
14
+ const VERSION = createRequire(import.meta.url)(join(dirname(fileURLToPath(import.meta.url)), "../package.json")).version;
15
+
16
+ const EXIT = {
17
+ USAGE: 1,
18
+ INVALID_ARGUMENT: 1,
19
+ NOT_FOUND: 2,
20
+ RATE_LIMITED: 3,
21
+ HTTP_ERROR: 3,
22
+ INVALID_RESPONSE: 3,
23
+ ABORTED: 3,
24
+ INTERNAL_ERROR: 3,
25
+ };
26
+
27
+ // --- I/O ---
28
+
29
+ process.stdout.on("error", (error) => {
30
+ if (error?.code === "EPIPE") process.exit(0);
31
+ throw error;
75
32
  });
76
33
 
77
- // === page mode: node search.mjs --page <book_id> <page_id> ===
78
- if (pageMode) {
79
- const [pBook, pPage] = numbers;
80
- if (!pBook || !pPage) fail("Usage: node search.mjs --page <book_id> <page_id>");
81
- const page = await turath.getPage(pBook, pPage);
82
- const context = await turath.getContext(page, { pagesBefore: 1, pagesAfter: 1 });
83
- emit(passageToJson(page, { text: page.text, context_text: context.text }));
84
- process.exit(0);
85
- }
86
-
87
- if (!query) fail('Usage: node search.mjs [--madhhab hanafi|maliki|shafii|hanbali] [--books "t1,t2"] [--book-id N] "<query>" [max_results]');
88
-
89
- // === build tiered queries ===
90
- const searchQueries = [];
91
- if (madhhab) {
92
- const label = MADHHAB_LABELS[madhhab];
93
- searchQueries.push({ q: `${query} ${label.at}`, tier: 1, source_type: `madhhab:${madhhab}` });
94
- searchQueries.push({ q: `${query} ${label.fi}`, tier: 1, source_type: `madhhab:${madhhab}` });
95
- searchQueries.push({ q: `${query} ${label.name}`, tier: 2, source_type: `madhhab:${madhhab}` });
96
- for (const book of MADHHAB_BOOKS[madhhab]) {
97
- searchQueries.push({ q: `${query} ${book}`, tier: 3, source_type: `book:${madhhab}` });
34
+ const writeJsonl = (record) => {
35
+ process.stdout.write(`${JSON.stringify(record)}\n`);
36
+ };
37
+
38
+ const fail = (code, error) => {
39
+ const payload = {
40
+ ok: false,
41
+ error: {
42
+ code,
43
+ message: error?.message ?? String(error),
44
+ ...(error?.status !== undefined && { status: error.status }),
45
+ ...(error?.url !== undefined && { url: error.url }),
46
+ ...(error?.retryAfter !== undefined && { retryAfter: error.retryAfter }),
47
+ },
48
+ };
49
+ process.stderr.write(`${JSON.stringify(payload)}\n`);
50
+ process.exit(EXIT[code] ?? 3);
51
+ };
52
+
53
+ const usage = (message) => fail("USAGE", { message });
54
+
55
+ // --- text format ---
56
+
57
+ const preview = (text, n = 160) => {
58
+ const one = String(text ?? "").replace(/\s+/g, " ").trim();
59
+ return one.length <= n ? one : `${one.slice(0, n)}…`;
60
+ };
61
+
62
+ const writeText = (record) => {
63
+ switch (record.type) {
64
+ case "meta": {
65
+ const parts = [`meta\t${record.command}`];
66
+ if (record.query !== undefined) parts.push(`q=${record.query}`);
67
+ if (record.returned !== undefined) parts.push(`returned=${record.returned}`);
68
+ if (record.totalMatches !== undefined) parts.push(`totalMatches=${record.totalMatches}`);
69
+ if (record.page !== undefined) parts.push(`page=${record.page}`);
70
+ process.stdout.write(`${parts.join("\t")}\n`);
71
+ break;
72
+ }
73
+ case "book":
74
+ process.stdout.write(
75
+ `book\t${record.id}\t${record.title}` +
76
+ (record.author?.name ? `\t${record.author.name}` : record.author?.id ? `\tauthor=${record.author.id}` : "") +
77
+ (record.toc ? `\ttoc=${record.toc.length}` : "") +
78
+ "\n",
79
+ );
80
+ break;
81
+ case "author":
82
+ process.stdout.write(`author\t${record.id}\t${record.name}\n`);
83
+ break;
84
+ case "category":
85
+ process.stdout.write(`category\t${record.id}\t${record.title}\tbooks=${record.bookCount}\n`);
86
+ break;
87
+ case "catalog":
88
+ process.stdout.write(
89
+ `catalog\tbooks=${record.bookCount}\tcategories=${record.categoryCount}\tauthors=${record.authorIdCount}\n`,
90
+ );
91
+ break;
92
+ case "passage":
93
+ process.stdout.write(
94
+ `passage\t${record.book?.id ?? "?"}\t${record.citation ?? ""}\t${record.url ?? ""}\t${preview(record.text)}\n`,
95
+ );
96
+ break;
97
+ default:
98
+ process.stdout.write(`${record.type}\t${JSON.stringify(record)}\n`);
98
99
  }
99
- } else if (books) {
100
- for (const title of books) {
101
- const match = turath.findBooks(title, { limit: 1 })[0];
102
- if (match) searchQueries.push({ q: query, tier: 3, source_type: "targeted", bookId: match.id, matched_term: title });
100
+ };
101
+
102
+ const emit = (format, record) => {
103
+ if (format === "text") writeText(record);
104
+ else writeJsonl(record);
105
+ };
106
+
107
+ // --- records ---
108
+
109
+ const toPassageRecord = (passage) => {
110
+ const record = {
111
+ type: "passage",
112
+ book: passage.book,
113
+ location: passage.location,
114
+ text: passage.text,
115
+ headings: passage.headings ?? [],
116
+ url: passage.url,
117
+ citation: passage.citation,
118
+ };
119
+ if (passage.author) record.author = passage.author;
120
+ if (passage.category) record.category = passage.category;
121
+ if (passage.snippet !== undefined) record.snippet = passage.snippet;
122
+ if (passage.locator) record.locator = passage.locator;
123
+ if (passage.provenance) record.provenance = passage.provenance;
124
+ return record;
125
+ };
126
+
127
+ const toBookRecord = (book) => {
128
+ const record = {
129
+ type: "book",
130
+ id: book.id,
131
+ title: book.title,
132
+ provider: book.provider ?? "turath",
133
+ };
134
+ if (book.author) record.author = book.author;
135
+ if (book.category) record.category = book.category;
136
+ if (book.description !== undefined) record.description = book.description;
137
+ if (book.hasPdf !== undefined) record.hasPdf = book.hasPdf;
138
+ if (book.toc) record.toc = book.toc;
139
+ if (book.volumes) record.volumes = book.volumes;
140
+ return record;
141
+ };
142
+
143
+ const toAuthorRecord = (author) => {
144
+ const record = {
145
+ type: "author",
146
+ id: author.id,
147
+ name: author.name,
148
+ provider: author.provider ?? "turath",
149
+ };
150
+ if (author.biography !== undefined) record.biography = author.biography;
151
+ if (author.deathYear !== undefined) record.deathYear = author.deathYear;
152
+ return record;
153
+ };
154
+
155
+ const toCategoryRecord = (category) => ({
156
+ type: "category",
157
+ id: category.id,
158
+ title: category.title,
159
+ provider: category.provider ?? "turath",
160
+ bookCount: category.bookCount,
161
+ });
162
+
163
+ const toCatalogRecord = (meta) => ({ type: "catalog", ...meta });
164
+
165
+ // --- parse helpers ---
166
+
167
+ const parseIntFlag = (raw, name, { def, min, max } = {}) => {
168
+ if (raw === undefined) return def;
169
+ if (raw === true) usage(`--${name} requires a value`);
170
+ if (!/^\d+$/.test(String(raw))) usage(`--${name} must be an integer`);
171
+ const n = Number(raw);
172
+ if (min !== undefined && n < min) usage(`--${name} must be >= ${min}`);
173
+ if (max !== undefined && n > max) usage(`--${name} must be <= ${max}`);
174
+ return n;
175
+ };
176
+
177
+ const parsePositiveId = (raw, label) => {
178
+ const value = String(raw);
179
+ if (!/^[1-9]\d*$/.test(value)) usage(`${label} must be a positive integer`);
180
+ return value;
181
+ };
182
+
183
+ const requireFlag = (flags, name) => {
184
+ const value = flags[name];
185
+ if (value === undefined || value === true) usage(`--${name} is required`);
186
+ return value;
187
+ };
188
+
189
+ const optionalFlag = (flags, name) => {
190
+ const value = flags[name];
191
+ if (value === undefined) return undefined;
192
+ if (value === true) usage(`--${name} requires a value`);
193
+ return value;
194
+ };
195
+
196
+ const parseIdList = (values, label, { max = 1 } = {}) => {
197
+ const list = values ?? [];
198
+ if (list.length > max) {
199
+ usage(max === 1 ? `at most one --${label}` : `at most ${max} --${label} values`);
103
200
  }
104
- }
105
- searchQueries.push({ q: query, tier: 4, source_type: "broad" });
106
-
107
- // === execute ===
108
- const seen = new Set();
109
- const collected = [];
110
- for (const sq of searchQueries) {
111
- if (collected.length >= maxResults * 3) break;
112
- try {
113
- const selectedBookId = sq.bookId ?? bookId;
114
- const result = await turath.search(sq.q, selectedBookId ? { bookIds: [selectedBookId] } : {});
115
- for (const hit of result.items) {
116
- const key = `${hit.book.id}:${hit.location.internalPage ?? ""}`;
117
- if (seen.has(key)) continue;
118
- seen.add(key);
119
- collected.push({ hit, tier: sq.tier, source_type: sq.source_type, matched_term: sq.matched_term ?? sq.q.replace(query, "").trim() });
201
+ return list.map((value) => parsePositiveId(value, label));
202
+ };
203
+
204
+ const takeQuery = (positionals, { required = true } = {}) => {
205
+ if (positionals.length > 1) usage("accepts a single <query> argument");
206
+ if (!positionals.length) {
207
+ if (required) usage("requires a <query>");
208
+ return "";
209
+ }
210
+ const query = positionals[0];
211
+ if (!String(query).trim()) {
212
+ if (required) usage("query must not be empty");
213
+ return "";
214
+ }
215
+ return query;
216
+ };
217
+
218
+ const takeIdArg = (positionals, label) => {
219
+ if (positionals.length === 0) usage(`${label} requires an id argument`);
220
+ if (positionals.length > 1) usage(`${label} accepts a single id argument`);
221
+ return parsePositiveId(positionals[0], `${label} id`);
222
+ };
223
+
224
+ const rejectExtras = (positionals, flags, multi) => {
225
+ if (positionals.length) usage("this command accepts no positional arguments");
226
+ if (Object.keys(flags).length) usage(`unknown option --${Object.keys(flags)[0]}`);
227
+ if (Object.keys(multi).length) usage(`unknown option --${Object.keys(multi)[0]}`);
228
+ };
229
+
230
+ const parseArgs = (argv) => {
231
+ const positionals = [];
232
+ const flags = Object.create(null);
233
+ const multi = Object.create(null);
234
+ const multiKeys = new Set(["author-id", "category-id"]);
235
+
236
+ for (let i = 0; i < argv.length; i += 1) {
237
+ const arg = argv[i];
238
+ if (arg === "--") {
239
+ positionals.push(...argv.slice(i + 1));
240
+ break;
241
+ }
242
+ if (arg === "-h" || arg === "--help") {
243
+ flags.help = true;
244
+ continue;
120
245
  }
121
- } catch {
122
- // skip failed tier queries; broad fallback still runs
246
+ if (arg === "-v" || arg === "--version") {
247
+ flags.version = true;
248
+ continue;
249
+ }
250
+ if (arg.startsWith("-") && !arg.startsWith("--")) {
251
+ usage(`unknown option ${arg}`);
252
+ }
253
+ if (!arg.startsWith("--")) {
254
+ positionals.push(arg);
255
+ continue;
256
+ }
257
+ const eq = arg.indexOf("=");
258
+ let key;
259
+ let value;
260
+ if (eq !== -1) {
261
+ key = arg.slice(2, eq);
262
+ value = arg.slice(eq + 1);
263
+ } else {
264
+ key = arg.slice(2);
265
+ const next = argv[i + 1];
266
+ if (next !== undefined && !next.startsWith("-")) {
267
+ value = next;
268
+ i += 1;
269
+ } else {
270
+ value = true;
271
+ }
272
+ }
273
+ if (!key) usage("invalid option");
274
+ if (multiKeys.has(key)) {
275
+ if (value === true) usage(`--${key} requires a value`);
276
+ (multi[key] ??= []).push(value);
277
+ continue;
278
+ }
279
+ if (flags[key] !== undefined) usage(`duplicate flag --${key}`);
280
+ flags[key] = value;
123
281
  }
124
- }
125
-
126
- collected.sort((a, b) => a.tier - b.tier);
127
- const top = collected.slice(0, maxResults);
128
-
129
- for (const { hit, tier, source_type, matched_term } of top) {
130
- let text = hit.text;
131
- let prevTail = "";
132
- let nextHead = "";
133
- if (hit.location.internalPage !== undefined) {
134
- try {
135
- const page = await turath.getPage(hit.book.id, hit.location.internalPage);
136
- text = page.text;
137
- const pg = hit.location.internalPage;
138
- if (pg > 1) {
139
- try { prevTail = (await turath.getPage(hit.book.id, pg - 1)).text.slice(-300); } catch {}
282
+
283
+ return { positionals, flags, multi };
284
+ };
285
+
286
+ const takeAllowed = (flags, multi, allowed) => {
287
+ const allowedSet = new Set(allowed);
288
+ const outFlags = Object.create(null);
289
+ const outMulti = Object.create(null);
290
+ for (const [key, value] of Object.entries(flags)) {
291
+ if (!allowedSet.has(key)) usage(`unknown option --${key}`);
292
+ outFlags[key] = value;
293
+ }
294
+ for (const [key, value] of Object.entries(multi)) {
295
+ if (!allowedSet.has(key)) usage(`unknown option --${key}`);
296
+ outMulti[key] = value;
297
+ }
298
+ return { flags: outFlags, multi: outMulti };
299
+ };
300
+
301
+ const parseLiveScope = (flags, multi) => {
302
+ const bookId = optionalFlag(flags, "book-id");
303
+ const authorIds = parseIdList(multi["author-id"], "author-id", { max: 1 });
304
+ const categoryIds = parseIdList(multi["category-id"], "category-id", { max: 1 });
305
+ const scope = {};
306
+ if (bookId !== undefined) scope.bookIds = [parsePositiveId(bookId, "book-id")];
307
+ if (authorIds.length) scope.authorIds = authorIds;
308
+ if (categoryIds.length) scope.categoryIds = categoryIds;
309
+ return scope;
310
+ };
311
+
312
+ // --- command table ---
313
+
314
+ const GLOBAL_FLAGS = ["format", "timeout"];
315
+
316
+ const COMMANDS = {
317
+ "find-books": {
318
+ help: `nusus find-books [query] [--limit N] [--author-id <id>]... [--category-id <id>]...
319
+ Offline catalog book search.
320
+ Query optional only when --author-id and/or --category-id is set.
321
+ --limit default 20, max 100
322
+ --author-id repeatable offline author filter (max ${MAX_FILTER_IDS})
323
+ --category-id repeatable offline category filter (max ${MAX_FILTER_IDS})`,
324
+ flags: [...GLOBAL_FLAGS, "limit", "author-id", "category-id"],
325
+ run: async ({ turath, format, positionals, flags, multi }) => {
326
+ const query = takeQuery(positionals, { required: false });
327
+ const limit = parseIntFlag(flags.limit, "limit", { def: 20, min: 1, max: 100 });
328
+ const authorIds = parseIdList(multi["author-id"], "author-id", { max: MAX_FILTER_IDS });
329
+ const categoryIds = parseIdList(multi["category-id"], "category-id", { max: MAX_FILTER_IDS });
330
+ if (!query.trim() && !authorIds.length && !categoryIds.length) {
331
+ usage("find-books requires a <query> or --author-id/--category-id");
140
332
  }
141
- try { nextHead = (await turath.getPage(hit.book.id, pg + 1)).text.slice(0, 300); } catch {}
142
- } catch {}
333
+ const books = turath.findBooks(query, {
334
+ limit,
335
+ ...(authorIds.length && { authorIds }),
336
+ ...(categoryIds.length && { categoryIds }),
337
+ });
338
+ emit(format, {
339
+ type: "meta",
340
+ command: "find-books",
341
+ query,
342
+ ...(authorIds.length && { authorIds }),
343
+ ...(categoryIds.length && { categoryIds }),
344
+ limit,
345
+ returned: books.length,
346
+ });
347
+ for (const book of books) emit(format, toBookRecord(book));
348
+ },
349
+ },
350
+ "find-authors": {
351
+ help: `nusus find-authors <query> [--limit N]
352
+ Offline catalog author search.
353
+ --limit default 20, max 100`,
354
+ flags: [...GLOBAL_FLAGS, "limit"],
355
+ run: async ({ turath, format, positionals, flags }) => {
356
+ const query = takeQuery(positionals);
357
+ const limit = parseIntFlag(flags.limit, "limit", { def: 20, min: 1, max: 100 });
358
+ const authors = turath.findAuthors(query, { limit });
359
+ emit(format, {
360
+ type: "meta",
361
+ command: "find-authors",
362
+ query,
363
+ limit,
364
+ returned: authors.length,
365
+ });
366
+ for (const author of authors) emit(format, toAuthorRecord(author));
367
+ },
368
+ },
369
+ "list-categories": {
370
+ help: `nusus list-categories
371
+ Offline category list.`,
372
+ flags: [...GLOBAL_FLAGS],
373
+ run: async ({ turath, format, positionals, flags, multi }) => {
374
+ rejectExtras(positionals, flags, multi);
375
+ for (const category of turath.listCategories()) emit(format, toCategoryRecord(category));
376
+ },
377
+ },
378
+ catalog: {
379
+ help: `nusus catalog
380
+ Offline catalog metadata.`,
381
+ flags: [...GLOBAL_FLAGS],
382
+ run: async ({ turath, format, positionals, flags, multi }) => {
383
+ rejectExtras(positionals, flags, multi);
384
+ emit(format, toCatalogRecord(turath.getCatalogMetadata()));
385
+ },
386
+ },
387
+ search: {
388
+ help: `nusus search <query> [--book-id ID] [--author-id ID] [--category-id ID]
389
+ [--page N] [--sort relevance|page]
390
+ Live Turath search (JSONL meta + passages).
391
+ --page default 1
392
+ --sort relevance (default) | page
393
+ At most one ID per filter; filters may be combined.`,
394
+ flags: [...GLOBAL_FLAGS, "book-id", "author-id", "category-id", "page", "sort"],
395
+ run: async ({ turath, format, positionals, flags, multi }) => {
396
+ const query = takeQuery(positionals);
397
+ const scope = parseLiveScope(flags, multi);
398
+ const page = parseIntFlag(flags.page, "page", { def: 1, min: 1 });
399
+ const sortRaw = optionalFlag(flags, "sort") ?? "relevance";
400
+ if (sortRaw !== "relevance" && sortRaw !== "page") usage("--sort must be relevance or page");
401
+ const result = await turath.search(query, {
402
+ ...scope,
403
+ page,
404
+ ...(sortRaw === "page" && { sort: "page" }),
405
+ });
406
+ emit(format, {
407
+ type: "meta",
408
+ command: "search",
409
+ query,
410
+ totalMatches: result.totalMatches,
411
+ page: result.page,
412
+ sort: sortRaw,
413
+ ...(scope.bookIds && { bookIds: scope.bookIds }),
414
+ ...(scope.authorIds && { authorIds: scope.authorIds }),
415
+ ...(scope.categoryIds && { categoryIds: scope.categoryIds }),
416
+ });
417
+ for (const item of result.items) emit(format, toPassageRecord(item));
418
+ },
419
+ },
420
+ retrieve: {
421
+ help: `nusus retrieve <query> [--book-id ID] [--author-id ID] [--category-id ID]
422
+ [--max-passages N] [--max-chars N]
423
+ [--pages-before N] [--pages-after N]
424
+ Live retrieve with citations/locators.
425
+ --max-passages default 5, max 20
426
+ --max-chars default 2000, max 20000
427
+ --pages-before default 0, max 2
428
+ --pages-after default 0, max 2
429
+ At most one ID per filter; filters may be combined.`,
430
+ flags: [
431
+ ...GLOBAL_FLAGS,
432
+ "book-id",
433
+ "author-id",
434
+ "category-id",
435
+ "max-passages",
436
+ "max-chars",
437
+ "pages-before",
438
+ "pages-after",
439
+ ],
440
+ run: async ({ turath, format, positionals, flags, multi }) => {
441
+ const query = takeQuery(positionals);
442
+ const scope = parseLiveScope(flags, multi);
443
+ const maxPassages = parseIntFlag(flags["max-passages"], "max-passages", { def: 5, min: 1, max: 20 });
444
+ const maxChars = parseIntFlag(flags["max-chars"], "max-chars", { def: 2000, min: 1, max: 20_000 });
445
+ const pagesBefore = parseIntFlag(flags["pages-before"], "pages-before", { def: 0, min: 0, max: 2 });
446
+ const pagesAfter = parseIntFlag(flags["pages-after"], "pages-after", { def: 0, min: 0, max: 2 });
447
+ const result = await turath.retrieve(query, {
448
+ maxPassages,
449
+ maxCharsPerPassage: maxChars,
450
+ pagesBefore,
451
+ pagesAfter,
452
+ ...(Object.keys(scope).length && { scope }),
453
+ });
454
+ emit(format, {
455
+ type: "meta",
456
+ command: "retrieve",
457
+ query,
458
+ totalMatches: result.totalMatches,
459
+ maxPassages,
460
+ maxChars,
461
+ pagesBefore,
462
+ pagesAfter,
463
+ ...(scope.bookIds && { bookIds: scope.bookIds }),
464
+ ...(scope.authorIds && { authorIds: scope.authorIds }),
465
+ ...(scope.categoryIds && { categoryIds: scope.categoryIds }),
466
+ });
467
+ for (const passage of result.passages) emit(format, toPassageRecord(passage));
468
+ },
469
+ },
470
+ "get-page": {
471
+ help: `nusus get-page --book-id <id> --page-id <id>
472
+ Fetch one internal page.
473
+ page-id is Turath internal page, not printed page.`,
474
+ flags: [...GLOBAL_FLAGS, "book-id", "page-id"],
475
+ run: async ({ turath, format, positionals, flags }) => {
476
+ if (positionals.length) usage("get-page takes --book-id and --page-id flags only");
477
+ const bookId = parsePositiveId(requireFlag(flags, "book-id"), "book-id");
478
+ const pageId = parsePositiveId(requireFlag(flags, "page-id"), "page-id");
479
+ const page = await turath.getPage(bookId, pageId);
480
+ emit(format, toPassageRecord(page));
481
+ },
482
+ },
483
+ "get-context": {
484
+ help: `nusus get-context --book-id <id> --page-id <id>
485
+ [--pages-before N] [--pages-after N]
486
+ Fetch page plus surrounding pages.
487
+ --pages-before default 1, max 3
488
+ --pages-after default 1, max 3
489
+ page-id is Turath internal page, not printed page.`,
490
+ flags: [...GLOBAL_FLAGS, "book-id", "page-id", "pages-before", "pages-after"],
491
+ run: async ({ turath, format, positionals, flags }) => {
492
+ if (positionals.length) usage("get-context takes --book-id and --page-id flags only");
493
+ const bookId = parsePositiveId(requireFlag(flags, "book-id"), "book-id");
494
+ const pageId = parsePositiveId(requireFlag(flags, "page-id"), "page-id");
495
+ const pagesBefore = parseIntFlag(flags["pages-before"], "pages-before", { def: 1, min: 0, max: 3 });
496
+ const pagesAfter = parseIntFlag(flags["pages-after"], "pages-after", { def: 1, min: 0, max: 3 });
497
+ const center = await turath.getPage(bookId, pageId);
498
+ const ctx = await turath.getContext(center, { pagesBefore, pagesAfter });
499
+ emit(format, toPassageRecord(ctx));
500
+ },
501
+ },
502
+ "get-book": {
503
+ help: `nusus get-book <book-id>
504
+ Book metadata + TOC (normalized from indexes).`,
505
+ flags: [...GLOBAL_FLAGS],
506
+ run: async ({ turath, format, positionals }) => {
507
+ const bookId = takeIdArg(positionals, "get-book");
508
+ const book = await turath.getBook(bookId);
509
+ emit(format, toBookRecord(book));
510
+ },
511
+ },
512
+ "get-author": {
513
+ help: `nusus get-author <author-id>
514
+ Author metadata.`,
515
+ flags: [...GLOBAL_FLAGS],
516
+ run: async ({ turath, format, positionals }) => {
517
+ const authorId = takeIdArg(positionals, "get-author");
518
+ const author = await turath.getAuthor(authorId);
519
+ emit(format, toAuthorRecord(author));
520
+ },
521
+ },
522
+ };
523
+
524
+ const GLOBAL_HELP = `nusus <command> [args] [options]
525
+
526
+ Commands:
527
+ find-books [query] Offline catalog book search (query optional with filters)
528
+ find-authors <query> Offline catalog author search
529
+ list-categories Offline category list
530
+ catalog Offline catalog metadata
531
+ search <query> Live Turath search (JSONL meta + passages)
532
+ retrieve <query> Live retrieve with citations/locators
533
+ get-page Fetch one internal page
534
+ get-context Fetch page plus surrounding pages
535
+ get-book <book-id> Book metadata + TOC
536
+ get-author <author-id> Author metadata
537
+
538
+ Global options:
539
+ --format jsonl|text Output format (default: jsonl)
540
+ --timeout <ms> Request timeout (default: ${DEFAULT_TIMEOUT}, 0 = no timeout, max ${MAX_TIMEOUT})
541
+ -h, --help Show help
542
+ -v, --version Print package version
543
+
544
+ Notes:
545
+ • page-id is Turath internal page (location.internalPage), not printed page.
546
+ • search/retrieve accept at most one --book-id, one --author-id, and one --category-id
547
+ (filters may be combined; multiple values of the same filter are rejected).
548
+ • find-books allows repeated --author-id/--category-id (offline catalog only).
549
+ • Errors are JSON on stderr; data only on stdout.
550
+ • Exit: 0 ok (incl. zero hits), 1 usage/invalid, 2 not found, 3 http/rate/abort/invalid-response/internal.
551
+
552
+ nusus help | nusus --help | nusus <command> --help
553
+ `;
554
+
555
+ const printHelp = (command) => {
556
+ if (command && COMMANDS[command]) {
557
+ process.stdout.write(`${COMMANDS[command].help}\n\nGlobal: --format jsonl|text --timeout <ms> --version\n`);
558
+ } else {
559
+ process.stdout.write(GLOBAL_HELP);
143
560
  }
144
- emit(passageToJson(hit, {
145
- source_type,
146
- tier,
147
- matched_term,
148
- snippet: hit.snippet,
149
- prev_text_tail: prevTail,
150
- text: text.slice(0, 1500),
151
- next_text_head: nextHead,
152
- }));
153
- }
154
-
155
- if (top.length === 0) emit({ error: "No results", query, madhhab });
561
+ };
562
+
563
+ const createClient = (timeout) => {
564
+ const options = { timeout };
565
+ // Test-only override used by CLI subprocess tests. Not a supported public config surface.
566
+ const baseUrl = process.env.NUSUS_TURATH_BASE_URL;
567
+ if (baseUrl) options.baseUrl = baseUrl;
568
+ return createTurathClient(options);
569
+ };
570
+
571
+ const run = async () => {
572
+ const argv = process.argv.slice(2);
573
+ if (argv.length === 0) {
574
+ printHelp();
575
+ process.exit(0);
576
+ }
577
+
578
+ const { positionals, flags, multi } = parseArgs(argv);
579
+
580
+ if (flags.version) {
581
+ const extraFlags = Object.keys(flags).filter((k) => k !== "version" && k !== "help");
582
+ if (positionals.length || Object.keys(multi).length || extraFlags.length) {
583
+ usage("--version does not accept other arguments");
584
+ }
585
+ process.stdout.write(`${VERSION}\n`);
586
+ process.exit(0);
587
+ }
588
+
589
+ let command = positionals[0];
590
+ let rest = positionals.slice(1);
591
+
592
+ if (flags.help && !command) {
593
+ printHelp();
594
+ process.exit(0);
595
+ }
596
+ if (command === "help") {
597
+ printHelp(rest[0]);
598
+ process.exit(0);
599
+ }
600
+ if (!command || !COMMANDS[command]) {
601
+ if (flags.help) {
602
+ printHelp();
603
+ process.exit(0);
604
+ }
605
+ usage(command ? `unknown command: ${command}` : "missing command");
606
+ }
607
+ if (flags.help) {
608
+ printHelp(command);
609
+ process.exit(0);
610
+ }
611
+
612
+ const spec = COMMANDS[command];
613
+ const { flags: cmdFlags, multi: cmdMulti } = takeAllowed(flags, multi, spec.flags);
614
+
615
+ const formatRaw = optionalFlag(cmdFlags, "format") ?? "jsonl";
616
+ if (formatRaw !== "jsonl" && formatRaw !== "text") usage("--format must be jsonl or text");
617
+ delete cmdFlags.format;
618
+
619
+ const timeout = parseIntFlag(cmdFlags.timeout, "timeout", {
620
+ def: DEFAULT_TIMEOUT,
621
+ min: 0,
622
+ max: MAX_TIMEOUT,
623
+ });
624
+ delete cmdFlags.timeout;
625
+
626
+ const turath = createClient(timeout);
627
+
628
+ try {
629
+ await spec.run({
630
+ turath,
631
+ format: formatRaw,
632
+ positionals: rest,
633
+ flags: cmdFlags,
634
+ multi: cmdMulti,
635
+ });
636
+ } catch (error) {
637
+ if (error instanceof NususError) fail(error.code, error);
638
+ fail("INTERNAL_ERROR", { message: error?.message ?? String(error) });
639
+ }
640
+ };
641
+
642
+ await run();