@autorag/librarian 2.3.0 → 2.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.
Files changed (82) hide show
  1. package/README.md +143 -16
  2. package/dist/agent/agent.d.ts +45 -13
  3. package/dist/agent/bash-tool.d.ts +2 -0
  4. package/dist/agent/index.d.ts +1 -1
  5. package/dist/agent/search-documents.d.ts +12 -3
  6. package/dist/agent/search-minsync-tool.d.ts +2 -2
  7. package/dist/cli/commands/evidence.d.ts +3 -0
  8. package/dist/cli/commands/index.d.ts +3 -3
  9. package/dist/cli/commands/refresh.d.ts +3 -4
  10. package/dist/cli/commands/search.d.ts +2 -2
  11. package/dist/cli/commands/tui.d.ts +36 -0
  12. package/dist/cli/commands/ui.d.ts +13 -0
  13. package/dist/cli/commands/watch.d.ts +1 -1
  14. package/dist/cli/config.d.ts +17 -19
  15. package/dist/cli/index.js +14497 -10906
  16. package/dist/cli/tui-commands.d.ts +16 -0
  17. package/dist/cli/tui-session-store.d.ts +16 -0
  18. package/dist/datasource/access-context.d.ts +6 -12
  19. package/dist/datasource/crawler-types.d.ts +2 -0
  20. package/dist/datasource/described-skill.d.ts +17 -0
  21. package/dist/datasource/index.d.ts +3 -3
  22. package/dist/datasource/result-filter.d.ts +2 -1
  23. package/dist/datasource/skills/clawgallery/client.d.ts +9 -0
  24. package/dist/datasource/skills/clawgallery/index.d.ts +5 -0
  25. package/dist/datasource/skills/clawgallery/methods.d.ts +19 -0
  26. package/dist/datasource/skills/clawgallery/paths.d.ts +7 -0
  27. package/dist/datasource/skills/clawgallery/skill.d.ts +35 -0
  28. package/dist/datasource/skills/clawgallery/types.d.ts +58 -0
  29. package/dist/datasource/skills/cloud-drive/index.d.ts +1 -0
  30. package/dist/datasource/skills/{gdrive → cloud-drive}/rclone-connector.d.ts +4 -0
  31. package/dist/datasource/skills/cloud-drive/skill.d.ts +1 -1
  32. package/dist/datasource/skills/discrawl/types.d.ts +4 -8
  33. package/dist/datasource/skills/factory.d.ts +2 -0
  34. package/dist/datasource/skills/gmail/index.d.ts +0 -1
  35. package/dist/datasource/skills/gmail/skill.d.ts +1 -1
  36. package/dist/datasource/skills/katok/methods.d.ts +6 -4
  37. package/dist/datasource/skills/katok/types.d.ts +8 -26
  38. package/dist/datasource/skills/mailcrawl/client.d.ts +15 -0
  39. package/dist/datasource/skills/mailcrawl/config.d.ts +2 -0
  40. package/dist/datasource/skills/mailcrawl/index.d.ts +4 -0
  41. package/dist/datasource/skills/mailcrawl/methods.d.ts +15 -0
  42. package/dist/datasource/skills/mailcrawl/skill.d.ts +34 -0
  43. package/dist/datasource/skills/mailcrawl/types.d.ts +71 -0
  44. package/dist/datasource/skills/obsidian/skill.d.ts +1 -0
  45. package/dist/datasource/skills/obsidian/types.d.ts +2 -0
  46. package/dist/datasource/types.d.ts +5 -2
  47. package/dist/filesystem/cloud-placeholder.d.ts +26 -0
  48. package/dist/index.d.ts +1 -1
  49. package/dist/index.js +2591 -1438
  50. package/dist/jikji/client.d.ts +3 -0
  51. package/dist/minsync/client.d.ts +5 -1
  52. package/dist/minsync/embedder-config.d.ts +4 -1
  53. package/dist/minsync/index.d.ts +4 -4
  54. package/dist/minsync/installer.d.ts +1 -0
  55. package/dist/minsync/method.d.ts +17 -8
  56. package/dist/minsync/types.d.ts +5 -0
  57. package/dist/mirror/sync.d.ts +1 -1
  58. package/dist/parser/defaults.d.ts +2 -0
  59. package/dist/parser/index.d.ts +2 -2
  60. package/dist/parser/opendataloader-pdf.d.ts +10 -0
  61. package/dist/parser/types.d.ts +7 -0
  62. package/dist/retrieval/index.d.ts +0 -2
  63. package/dist/retrieval/registry.d.ts +2 -2
  64. package/dist/retrieval/types.d.ts +1 -1
  65. package/dist/ui/browse.d.ts +11 -0
  66. package/dist/ui/catalog.d.ts +59 -0
  67. package/dist/ui/config-store.d.ts +43 -0
  68. package/dist/ui/discover.d.ts +17 -0
  69. package/dist/ui/html.d.ts +2 -0
  70. package/dist/ui/probe.d.ts +24 -0
  71. package/dist/ui/prompt.d.ts +21 -0
  72. package/dist/ui/server.d.ts +28 -0
  73. package/package.json +13 -12
  74. package/skills/autorag/SKILL.md +64 -43
  75. package/skills/autorag-setup/SKILL.md +76 -39
  76. package/dist/agent/search-bm25-tool.d.ts +0 -18
  77. package/dist/datasource/skills/discrawl/config.d.ts +0 -9
  78. package/dist/datasource/skills/gdrive/connector.d.ts +0 -30
  79. package/dist/datasource/skills/gdrive/index.d.ts +0 -3
  80. package/dist/datasource/skills/gdrive/skill.d.ts +0 -24
  81. package/dist/datasource/skills/gmail/himalaya-connector.d.ts +0 -40
  82. package/dist/retrieval/methods/bm25.d.ts +0 -52
package/README.md CHANGED
@@ -16,6 +16,25 @@ AutoRAG itself is the specialized search agent, not a coordinator for other
16
16
  model roles. You configure one model, and that model owns the complete
17
17
  retrieval, reading, judgment, and curation loop.
18
18
 
19
+ ## Core values
20
+
21
+ Three principles drive every design decision:
22
+
23
+ 1. **Never migrate your data to search it.** AutoRAG federates CLI-owned
24
+ stores (katok, discrawl, qmd, msgvault, rclone, …) in place. No forced
25
+ ingestion into a central index, no third-party server holding a copy of
26
+ your corpus. Results carry source-native identities
27
+ (`kakao:<chat>/<sender>/<chunk>`) with scope-checked access — see the
28
+ [competitive landscape study](docs/competitive-landscape-2026-09.md) for
29
+ why this is the durable differentiator.
30
+ 2. **Just works — no RAG degree required.** Install it and it works: minimal
31
+ configuration, no pipeline tuning, no vector-DB operations, no API keys.
32
+ The local embedder (EmbeddingGemma via Ollama) and MinSync auto-install
33
+ handle the "RAG plumbing" invisibly.
34
+ 3. **Fast by design.** One configured model owns the whole loop; retrieval
35
+ runs locally over MinSync CDC chunks (BM25 / vector / hybrid); interactive
36
+ search is optimized for low latency.
37
+
19
38
  ## Why AutoRAG
20
39
 
21
40
  ### The problem with search tools
@@ -65,26 +84,62 @@ Different documents need different search strategies:
65
84
  | Legal documents, specifications | BM25 (keyword ranking) | Handles domain terminology well |
66
85
  | Mixed collections | Hybrid (vector + BM25) | Combines precision and recall |
67
86
 
68
- AutoRAG supports **pluggable retrieval methods**. It ships with lexical **BM25** and semantic **MinSync** methods wired through the `RetrievalMethodRegistry`, and the architecture is ready for additional vector and hybrid backends. The librarian invokes retrieval tools, reads the underlying documents directly through `bash`, and curates one unified result set after `ResultMerger` score normalization and deduplication.
87
+ AutoRAG supports **pluggable retrieval methods**. Local lexical BM25, semantic vector, and hybrid retrieval all go through **MinSync** over one shared CDC chunk lifecycle, wired through the `RetrievalMethodRegistry`. The librarian invokes retrieval tools, reads the underlying documents directly through `bash`, and curates one unified result set after `ResultMerger` score normalization and deduplication. External datasources keep their own archive/index lifecycle.
88
+
89
+ BM25, vector, and hybrid are **enabled by default** whenever MinSync is enabled. Disable local indexing with `"minSync": false`, or disable only lexical search with `"bm25": false`. MinSync auto-installs a verified release into `<workspace>/.autorag/bin` on first use (`autoInstall: true`); set `"autoInstall": false` only when managing the binary yourself. Configure `minSync.embedder` via `autorag init --embedder-*` flags for remote embedding endpoints, and set `minSync.maxChunkSize` (or `--minsync-max-chunk-size`) when a local embedder has a smaller context window. AutoRAG never forces TEI or any external embedding service.
69
90
 
70
- BM25 and MinSync are **enabled by default** — no explicit configuration is needed for standard lexical + semantic retrieval. Both can be disabled by setting `"bm25": false` or `"minSync": false` in the config file. MinSync uses a pre-installed binary (`autoInstall: false`); configure `minSync.embedder` via `autorag init --embedder-*` flags for remote embedding endpoints. AutoRAG never forces TEI or any external embedding service.
91
+ See [docs/minsync-setup.md](docs/minsync-setup.md) for automatic installation,
92
+ managed binary paths, and the local EmbeddingGemma QA flow.
71
93
 
72
94
  ### Real directory access
73
95
 
74
- AutoRAG reads configured source directories directly through its built-in `bash` tool. Retrieval tools can supply candidate paths, but the same agent opens the source material before curating. Answers are returned as a structured `SearchDocumentsResponse`; results carry their real source (file path or datasource id) in the internal mapping for feedback and curation. BM25 and MinSync index parsed markdown mirrors under `.autorag`.
96
+ AutoRAG reads configured source directories directly through its built-in `bash` tool. Retrieval tools can supply candidate paths, but the same agent opens the source material before curating. Answers are returned as a structured `SearchDocumentsResponse`; results carry their real source (file path or datasource id) in the internal mapping for feedback and curation. MinSync indexes parsed markdown mirrors under `.autorag` for BM25, vector, and hybrid retrieval.
97
+
98
+ ### Thin PDF extraction retry
99
+
100
+ The default PDF parser performs a cheap quality check for multi-page PDFs.
101
+ When local markdown is unusually sparse (fewer than 800 characters or fewer
102
+ than 40 characters per detected page, for at least three pages), it retries
103
+ through OpenDataLoader's `docling-fast` hybrid backend with `hybridMode:
104
+ "auto"` and a 30-second timeout. Dense PDFs are not retried, and hybrid is
105
+ never used for single-page PDFs or as the first path for images.
106
+
107
+ The gate is parser-owned and can be tuned through trusted programmatic
108
+ `parserOptions`:
109
+
110
+ ```typescript
111
+ new AutoRAGAgent({
112
+ searchPaths: ["/path/to/documents"],
113
+ parserOptions: {
114
+ thinExtract: {
115
+ minPages: 3,
116
+ minChars: 800,
117
+ minCharsPerPage: 40,
118
+ timeoutMs: 30_000,
119
+ hybrid: "docling-fast",
120
+ hybridMode: "auto",
121
+ },
122
+ },
123
+ });
124
+ ```
125
+
126
+ If the hybrid sidecar is missing, times out, or fails, AutoRAG keeps the local
127
+ markdown and emits `pdf-extract-thin` plus `pdf-hybrid-unavailable`
128
+ diagnostics; refresh remains successful.
75
129
 
76
- ### Optional Jikji discovery and indexing
130
+ ### Default Jikji discovery and indexing
77
131
 
78
- AutoRAG can opt into [Jikji](https://github.com/NomaDamas/jikji) as a local CLI-backed **find-first discovery and indexing** layer. Jikji is optional: AutoRAG does not vendor it, install it, or register it as a retrieval backend when enabled.
132
+ AutoRAG uses [Jikji](https://github.com/NomaDamas/jikji) by default as a local CLI-backed **find-first discovery and indexing** layer. Jikji is not registered as a retrieval backend: it supplies bounded discovery answer packs while the librarian still reads original files directly.
79
133
 
80
- When Jikji is configured, AutoRAG calls `jikji find ROOT "query" --json` via the `jikji_find` tool. The tool parses and validates the upstream answer pack and exposes its `handoff_action`, `tool_call_policy`, and `agent_should_not_rerank` to the librarian. Direct file reading remains available for source verification. `prepare`/`refresh` remain for indexing only and do not answer queries directly.
134
+ The default agent calls `jikji find ROOT "query" --json` via the `jikji_find` tool. The tool parses and validates the upstream answer pack and exposes its `handoff_action`, `tool_call_policy`, and `agent_should_not_rerank` to the librarian. Direct file reading remains available for source verification. `prepare`/`refresh` remain for indexing only and do not answer queries directly. If the binary or Rust toolchain is unavailable, Jikji reports a diagnostic and the normal filesystem/retrieval paths continue.
135
+
136
+ New `autorag init` configs include `"jikji": {}`. To opt out, set `"jikji": false` in `config.json`; programmatic callers can pass `jikji: false`.
81
137
 
82
138
  Programmatic use:
83
139
 
84
140
  ```typescript
85
141
  const agent = new AutoRAGAgent({
86
142
  searchPaths: ["/path/to/documents"],
87
- jikji: { binaryPath: "jikji" },
88
143
  });
89
144
  await agent.prepareJikji();
90
145
  ```
@@ -118,7 +173,7 @@ searchable:
118
173
  If `dupey` is not installed or fails, refresh continues without exclusion and
119
174
  reports no destructive action; install it with `cargo install dupey`.
120
175
 
121
- The same `.autorag/jikji.json` shape configures Jikji when present:
176
+ The same configuration shape customizes Jikji when needed:
122
177
 
123
178
  ```json
124
179
  {
@@ -153,7 +208,7 @@ channel or group chat as its own datasource.
153
208
 
154
209
  Security defaults are intentionally strict:
155
210
 
156
- - datasource access is default-deny unless trusted server/API configuration supplies `datasourceAccess.allowedTags` and `datasourceAccess.allowedScopes`;
211
+ - datasource access is default-deny unless trusted server/API configuration supplies `datasourceAccess.allowedTags`; `allowedScopes` applies only to datasource methods that advertise the `scoped` capability;
157
212
  - model/tool arguments never grant datasource tags or scopes;
158
213
  - `search_datasource_documents` accepts only `{ query, topK?, scope? }`, and `scope` can only narrow trusted access.
159
214
 
@@ -169,8 +224,9 @@ Security defaults are intentionally strict:
169
224
  | Notion | `notion` | external [`notcrawl`](https://github.com/openclaw/notcrawl) CLI | local-first page/database/block archive + FTS5 search |
170
225
  | GitHub Issues/PRs | `github` | GitHub REST (token optional) | issues + PR bodies per `owner/repo`; public repos work unauthenticated |
171
226
  | Cloud drives | `cloud-drive` | **[`rclone`](https://rclone.org) CLI** | Incremental Google Drive Tier-1; OneDrive/network remotes; iCloud experimental |
172
- | Gmail / IMAP | `gmail` | Gmail REST v1, or **[`himalaya`](https://pimalaya.org) CLI** (`backend: "himalaya"`) | the himalaya backend indexes any IMAP/Maildir account it has configured — no OAuth plumbing |
227
+ | Gmail | `gmail` | Gmail REST v1 | OAuth access token is referenced by environment variable name |
173
228
  | Local mail exports | `mail-export` | filesystem (`.mbox` / `.eml`) | classic `From_` splitting, mailparser-based; count-only warnings |
229
+ | Mail archives | `mailcrawl` | external [`mailcrawl`](https://github.com/NomaDamas/mailcrawl) CLI | local email sync plus BM25, semantic, and hybrid retrieval |
174
230
  | Obsidian vault | `obsidian` | external [`qmd`](https://github.com/tobi/qmd) CLI | incremental `qmd update`, BM25 `qmd search`, semantic `qmd vsearch`; vault path via `connector.vaultPath` |
175
231
  | RSS / news | `rss` | HTTP feed polling | RSS 2.0 + Atom, feed/category hierarchy, 24h dedupe window |
176
232
 
@@ -186,15 +242,16 @@ Configure them in `config.json` (CLI) or pass `datasourceSkills` programmaticall
186
242
  "slack": { "connector": { "binaryPath": "slacrawl", "configPath": "/path/to/slacrawl.yaml", "syncSource": "primary" } },
187
243
  "notion": { "connector": { "binaryPath": "notcrawl", "configPath": "/path/to/notcrawl.yaml" } },
188
244
  "github": { "connector": { "repos": ["owner/repo"] } },
189
- "gmail": { "connector": { "backend": "himalaya", "account": "gmail", "folder": "INBOX" } },
245
+ "gmail": { "connector": { "tokenEnv": "GMAIL_ACCESS_TOKEN", "labelIds": ["INBOX"] } },
246
+ "mailcrawl": { "instanceId": "personal", "connector": { "account": "personal", "mailbox": "INBOX", "binaryPath": "mailcrawl" } },
190
247
  "personal-google-drive": { "type": "cloud-drive", "instanceId": "personal", "connector": { "provider": "google-drive", "remote": "personal-gdrive:", "include": ["**/*.md"] } },
191
248
  "company-onedrive": { "type": "cloud-drive", "instanceId": "work", "connector": { "provider": "onedrive", "remote": "company-onedrive:Documents" } },
192
249
  "obsidian": { "connector": { "vaultPath": "/path/to/vault" } },
193
250
  "rss": { "connector": { "feeds": [{ "url": "https://example.com/feed.xml" }] } }
194
251
  },
195
252
  "datasourceAccess": {
196
- "allowedTags": ["whatsapp", "telegram", "slack", "notion", "github", "gmail", "cloud-drive", "obsidian", "rss"],
197
- "allowedScopes": ["/whatsapp/**", "/telegram/**", "/slack/**", "/notion/**", "/github/**", "/gmail/**", "/personal-google-drive/**", "/company-onedrive/**", "/obsidian/**", "/rss/**"]
253
+ "allowedTags": ["whatsapp", "telegram", "slack", "notion", "github", "gmail", "mailcrawl", "cloud-drive", "obsidian", "rss"],
254
+ "allowedScopes": ["/whatsapp/**", "/telegram/**", "/slack/**", "/notion/**", "/github/**", "/gmail/**", "/mailcrawl/**", "/personal-google-drive/**", "/company-onedrive/**", "/obsidian/**", "/rss/**"]
198
255
  }
199
256
  }
200
257
  ```
@@ -207,6 +264,18 @@ Install slacrawl with `brew install openclaw/tap/slacrawl`. AutoRAG invokes `sla
207
264
 
208
265
  Install notcrawl with `brew install openclaw/tap/notcrawl`. AutoRAG invokes `notcrawl sync` during datasource refresh and `notcrawl search --json` during retrieval. Optional trusted connector fields are `binaryPath` and `configPath`. Notion credentials and workspace definitions remain in notcrawl's own configuration rather than AutoRAG.
209
266
 
267
+ Install and configure [`mailcrawl`](https://github.com/NomaDamas/mailcrawl)
268
+ `@nomadamas/mailcrawl@0.1.4` or newer separately. AutoRAG invokes
269
+ `mailcrawl sync` followed by `mailcrawl index` during datasource refresh, then
270
+ calls `mailcrawl search` in BM25, semantic, or hybrid mode. The archive remains
271
+ in mailcrawl's native store unless the operator explicitly sets
272
+ `connector.dataDir`; Himalaya credentials and provider configuration remain
273
+ owned by mailcrawl. 0.1.3 and earlier fail a repeated
274
+ `index` after a no-op sync.
275
+ Use `mailcrawl` for all Himalaya-backed IMAP/Maildir retrieval. The retired
276
+ `gmail` connector option `backend: "himalaya"` is no longer registered; migrate
277
+ that configuration to an explicit `mailcrawl` datasource.
278
+
210
279
  Install and authenticate rclone separately (`brew install rclone && rclone
211
280
  config` on macOS), then configure the provider-neutral `cloud-drive` skill.
212
281
  `cloud-drive` is a reusable `type`: each datasource config key is a connection
@@ -240,7 +309,6 @@ const agent = new AutoRAGAgent({
240
309
  datasourceSkills: [kakao],
241
310
  datasourceAccess: {
242
311
  allowedTags: ["kakaotalk"],
243
- allowedScopes: ["/kakao/personal/**"],
244
312
  },
245
313
  });
246
314
 
@@ -271,7 +339,6 @@ const agent = new AutoRAGAgent({
271
339
  datasourceSkills: [discord],
272
340
  datasourceAccess: {
273
341
  allowedTags: ["discord"],
274
- allowedScopes: ["/discord/community/**"],
275
342
  },
276
343
  });
277
344
  ```
@@ -297,7 +364,7 @@ Or through the trusted config factory:
297
364
  Two defaults are deliberate and worth keeping:
298
365
 
299
366
  - **`defaultMode: "hybrid"`** — discrawl's FTS index strips newlines without substituting a space, welding words across line breaks into a single unsearchable token (measured at ~47% of post-newline words on a real archive). Semantic recall covers that gap. See [#1413](https://github.com/Marker-Inc-Korea/AutoRAG/issues/1413).
300
- - **`embeddingProvider: "ollama"` + `embeddingModel: "embeddinggemma"`** — semantic search requires an embedding provider (`ollama serve && ollama pull embeddinggemma`). For workspace-managed discrawl state, AutoRAG writes these values to `.autorag/datasources/discrawl/config.toml` while preserving unrelated discrawl settings. An explicit `connector.configPath` remains operator-owned and is never rewritten. EmbeddingGemma (Gemma 3 300M, 768-dim, 100+ languages) is the same model family katok uses for KakaoTalk, so all CLI-backed datasources share one local embedder. Do **not** use `nomic-embed-text`: it is English-only and collapses non-English text into one narrow similarity band, silently degrading semantic search to noise. AutoRAG emits a diagnostic when an English-only model is configured. See [#1414](https://github.com/Marker-Inc-Korea/AutoRAG/issues/1414).
367
+ - **`embeddingProvider: "ollama"` + `embeddingModel: "embeddinggemma"`** — semantic search requires an embedding provider (`ollama serve && ollama pull embeddinggemma`). Configure these values in discrawl's own config; AutoRAG uses discrawl's native store unless an explicit `connector.configPath` is supplied. EmbeddingGemma (Gemma 3 300M, 768-dim, 100+ languages) is the same model family katok uses for KakaoTalk, so all CLI-backed datasources can share one local embedder. Do **not** use `nomic-embed-text`: it is English-only and collapses non-English text into one narrow similarity band, silently degrading semantic search to noise. AutoRAG emits a diagnostic when an English-only model is configured. See [#1414](https://github.com/Marker-Inc-Korea/AutoRAG/issues/1414).
301
368
 
302
369
  A datasource skill should provide polling/cron metadata for routine indexing, source descriptions for the agent prompt, slash-hierarchical opaque source paths such as `/kakao/personal/chunks/<chunk-id>`, and permission tags that match your server-side access policy.
303
370
 
@@ -349,6 +416,30 @@ Config path precedence is `--config` > `AUTORAG_CONFIG` > `~/.autorag/config.jso
349
416
 
350
417
  `autorag health` checks model/provider auth before a search — it resolves the model, verifies credential presence, and optionally probes one completion call. Use it to diagnose model, provider, auth, or timeout failures. `autorag status` remains the model-free index-health command (corpus freshness and BM25/MinSync readiness). When `autorag search` fails for a model/provider reason, the error output includes a hint pointing to `autorag health`.
351
418
 
419
+ `autorag ui` opens a loopback-only page (`127.0.0.1`) to connect local folders and datasource skills without editing JSON. It writes the same trusted `datasources` / `datasourceAccess` fields as a hand-edited config, stores env-var *names* rather than secrets, and refuses non-loopback binds. Use `--no-open` to print the URL without launching a browser.
420
+
421
+ For a deliberately deployed UI, opt in explicitly in `config.json`. Keep the
422
+ session token in the environment, set the public URL used by the browser, and
423
+ allow only the frontend origins that should make credentialed API requests:
424
+
425
+ ```json
426
+ {
427
+ "ui": {
428
+ "host": "0.0.0.0",
429
+ "port": 8787,
430
+ "allowRemote": true,
431
+ "publicOrigin": "https://autorag.example.com",
432
+ "corsOrigins": ["https://autorag.example.com"],
433
+ "tokenEnv": "AUTORAG_UI_TOKEN"
434
+ }
435
+ }
436
+ ```
437
+
438
+ Start it with `AUTORAG_UI_TOKEN` set to a random value of at least 16
439
+ characters. Local use remains the safe default: omit `ui` (or leave
440
+ `allowRemote` false) and AutoRAG binds to loopback, including a working
441
+ `localhost` URL on systems that resolve it to IPv6.
442
+
352
443
  ## Installation
353
444
 
354
445
  Published as `@autorag/librarian` (dist bundled with Bun, runtime Node ≥ 24 or Bun):
@@ -360,9 +451,45 @@ bun install -g @autorag/librarian # autorag CLI
360
451
  bun add github:NomaDamas/AutoRAG-2.0
361
452
  ```
362
453
 
454
+ PDF parsing requires **Java 11 or newer** because the bundled
455
+ `@opendataloader/pdf` runtime is compiled for Java 11. This applies to
456
+ Windows, macOS, and Linux. Verify the runtime that will be selected from
457
+ `PATH` before indexing PDFs:
458
+
459
+ ```bash
460
+ java -version
461
+ ```
462
+
463
+ On Windows, ensure a Java 11+ installation appears before older Java
464
+ installations in `PATH` (or set `JAVA_HOME` to the Java 11+ installation and
465
+ start a new shell). Java 8 is not supported by the PDF parser.
466
+
363
467
  Git-based installs build `dist/` via the `prepare` script and require Bun on the installing machine.
364
468
  External tool binaries auto-install on first use into `<workspace>/.autorag/bin`: **MinSync** downloads a verified GitHub release asset (on by default; `minSync.autoInstall: false` to opt out), and **Jikji** compiles the [`jikji-cli`](https://crates.io/crates/jikji-cli) crate via cargo (requires the [Rust toolchain](https://rustup.rs); `jikji.autoInstall: false` to opt out). New `autorag init` configs enable Jikji find-first discovery by default. KakaoTalk (`katok`) and Discord (`discrawl`) stay manual, optional installs. All of them degrade gracefully when missing — core BM25 search works without any of them.
365
469
 
470
+ ### Experimental TUI (beta)
471
+
472
+ The latest TUI work is currently available on the `feat/experimental-tui`
473
+ branch. To use this beta version from source:
474
+
475
+ ```bash
476
+ git clone --branch feat/experimental-tui https://github.com/Marker-Inc-Korea/AutoRAG.git
477
+ cd AutoRAG
478
+ bun install
479
+ bun run build
480
+
481
+ # Configure your document roots and model once:
482
+ node dist/cli/index.js init --search-paths /path/to/documents
483
+
484
+ # Start the beta TUI:
485
+ node dist/cli/index.js tui
486
+ ```
487
+
488
+ Inside the TUI, use `/resume` to browse saved sessions. Selecting a session
489
+ replaces the current view with that session instead of mixing the two
490
+ conversation histories. This branch is experimental and may change before the
491
+ feature is included in a published release.
492
+
366
493
  ## Quick Start
367
494
 
368
495
  ```typescript
@@ -8,19 +8,18 @@ import type { ResultFeedback } from "../memory/memory.js";
8
8
  import { type MinSyncSyncResult, type MinSyncVectorMethodOptions } from "../minsync/index.js";
9
9
  import { type ParsedMirrorSyncResult } from "../mirror/sync.js";
10
10
  import type { DefaultParserRegistryOptions } from "../parser/index.js";
11
- import { type BM25MethodOptions, type BM25SyncResult } from "../retrieval/methods/bm25.js";
12
11
  import { RetrievalMethodRegistry } from "../retrieval/registry.js";
13
12
  import type { CuratedResult, RetrievalDiagnostic, RetrievalOptions, RetrievalResult } from "../retrieval/types.js";
14
13
  import { type ScanDuplicateDocumentsDetails } from "./dupey-tool.js";
15
14
  import { type JikjiFindProviderResult } from "./jikji-find-tool.js";
16
- import { type SearchDocumentDiagnostic, type SearchDocumentsResponse } from "./search-documents.js";
15
+ import { type SearchDocumentDiagnostic, type SearchDocumentsResponse, type SearchDocumentsStreamEvent } from "./search-documents.js";
17
16
  import { type WatcherFactory, type WatchRefreshHandle } from "./watch-refresh.js";
18
17
  export interface AutoRefreshOptions {
19
18
  readonly intervalMs: number;
20
19
  readonly immediate?: boolean;
21
20
  }
22
21
  /** Methods that `refresh` can selectively run. Defaults to all when omitted. */
23
- export type RefreshMethod = "parsed" | "bm25" | "minsync" | "datasources" | "jikji";
22
+ export type RefreshMethod = "parsed" | "minsync" | "datasources" | "jikji";
24
23
  export interface AutoRAGRefreshOptions {
25
24
  /** Restrict refresh to specific methods. Defaults to all when undefined. */
26
25
  readonly methods?: readonly RefreshMethod[];
@@ -30,13 +29,12 @@ export interface AutoRAGMinSyncRefreshResult {
30
29
  readonly synced: number;
31
30
  readonly reason?: string;
32
31
  }
33
- export interface AutoRAGRefreshResult extends ParsedMirrorSyncResult {
34
- readonly bm25?: BM25SyncResult;
32
+ export interface AutoRAGRefreshResult extends Omit<ParsedMirrorSyncResult, "diagnostics"> {
33
+ readonly diagnostics: readonly SearchDocumentDiagnostic[];
35
34
  readonly minsync?: AutoRAGMinSyncRefreshResult;
36
35
  readonly datasources?: readonly DatasourceIndexResult[];
37
36
  }
38
37
  export interface AutoRAGRefreshComponentStatus {
39
- readonly bm25?: string;
40
38
  readonly minsync?: string;
41
39
  readonly jikji?: string;
42
40
  readonly datasources?: string;
@@ -77,14 +75,19 @@ export interface AutoRAGAgentOptions {
77
75
  workspacePath?: string;
78
76
  tools?: AgentTool[];
79
77
  minSync?: Omit<MinSyncVectorMethodOptions, "root"> | false;
80
- bm25?: Omit<BM25MethodOptions, "root"> | false;
81
- jikji?: JikjiOptions;
78
+ jikji?: JikjiOptions | false;
82
79
  autoRefresh?: AutoRefreshOptions;
83
80
  parserOptions?: DefaultParserRegistryOptions;
84
81
  dupey?: DupeyCliOptions | false;
85
82
  excludeExactDuplicates?: boolean;
86
83
  datasourceSkills?: readonly DatasourceSkill[];
87
84
  datasourceAccess?: DatasourceAccessContextOptions;
85
+ /** Non-fatal diagnostics from config/agent construction (e.g. skipped unknown datasources). */
86
+ startupDiagnostics?: readonly SearchDocumentDiagnostic[];
87
+ /** Maximum time a model/tool search may run before it is aborted. */
88
+ searchTimeoutMs?: number;
89
+ /** Maximum number of retrieval/tool executions allowed in one search. */
90
+ maxSearchToolCalls?: number;
88
91
  }
89
92
  export interface AutoRAGSearchSession {
90
93
  readonly agent: Agent;
@@ -119,6 +122,10 @@ export declare class AutoRAGAgent {
119
122
  private resultCapture;
120
123
  private autoRefreshTimer;
121
124
  private refreshing;
125
+ private jikjiPrepareInFlight;
126
+ private jikjiReady;
127
+ private minSyncPrepareInFlight;
128
+ private minSyncReady;
122
129
  private refreshState;
123
130
  private readonly searchPaths;
124
131
  private readonly configuredSearchPaths;
@@ -130,22 +137,27 @@ export declare class AutoRAGAgent {
130
137
  private readonly merger;
131
138
  private readonly datasourceFilter;
132
139
  private readonly minSyncMethod;
133
- private readonly bm25Method;
134
140
  private readonly jikjiClient;
135
141
  private readonly datasourceSkills;
136
142
  private readonly datasourceAccessOptions;
143
+ private readonly startupDiagnostics;
137
144
  private readonly datasourceAgentSkills;
138
145
  private readonly parserOptions;
139
146
  private readonly dupeyOptions;
140
147
  private readonly excludeExactDuplicates;
141
148
  private readonly baseSystemPromptConfig;
142
149
  private readonly droppedCallerToolNames;
150
+ private readonly searchTimeoutMs;
151
+ private readonly maxSearchToolCalls;
152
+ private searchToolCallCount;
153
+ private readonly sourceSearchCallCounts;
143
154
  constructor(options: AutoRAGAgentOptions);
144
155
  scanDuplicateDocuments(): Promise<ScanDuplicateDocumentsDetails>;
145
156
  private withMemoryContext;
146
157
  private resolveSessionModel;
147
158
  private createSearchSession;
148
159
  private configureSearchSession;
160
+ private withPerSourceSearchLimit;
149
161
  private recordSearchToolEvent;
150
162
  private currentSystemPromptConfig;
151
163
  subscribe(listener: Parameters<Agent["subscribe"]>[0]): () => void;
@@ -166,6 +178,13 @@ export declare class AutoRAGAgent {
166
178
  recordFeedbackByIds(usefulFeedbackIds: readonly string[], notUsefulFeedbackIds?: readonly string[]): void;
167
179
  getResultRegistry(sessionId?: string): ReadonlyMap<number, CuratedResult>;
168
180
  searchDocuments(query: string, options?: RetrievalOptions): Promise<SearchDocumentsResponse>;
181
+ /**
182
+ * Stream bounded progress updates while retaining the stable
183
+ * {@link searchDocuments} promise API. Progress is sourced from model text
184
+ * deltas, so callers can render it in a TUI, CLI, or parent agent and can
185
+ * stop by calling {@link abort}.
186
+ */
187
+ searchDocumentsStream(query: string, options?: RetrievalOptions): AsyncGenerator<SearchDocumentsStreamEvent, void, void>;
169
188
  private datasourceAccessContext;
170
189
  /**
171
190
  * Build the Pi agent-skill list for datasource skills authorized by the
@@ -180,9 +199,17 @@ export declare class AutoRAGAgent {
180
199
  */
181
200
  loadDatasourceSkill(name: string): Skill | undefined;
182
201
  private indexDatasources;
183
- /** Path-opaque component diagnostics (e.g. BM25 readiness) for the search response. */
202
+ /** Path-opaque component diagnostics for the search response. */
184
203
  private collectComponentDiagnostics;
185
- buildSearchPrompt(query: string, options: RetrievalOptions): string;
204
+ private prefetchInitialRetrievalContext;
205
+ /**
206
+ * Prepare MinSync lazily without holding up the current search. Parsed
207
+ * mirrors are built first because MinSync stages and indexes those mirrors.
208
+ */
209
+ /** Prepare MinSync in the background; the returned promise is for tests. */
210
+ scheduleMinSyncPrepareForTest(): Promise<MinSyncSyncResult | undefined> | undefined;
211
+ private scheduleMinSyncPrepare;
212
+ buildSearchPrompt(query: string, options: RetrievalOptions, initialRetrievalContext?: string): string;
186
213
  refresh(force?: boolean, opts?: AutoRAGRefreshOptions): Promise<AutoRAGRefreshResult>;
187
214
  /**
188
215
  * Path-opaque snapshot of corpus freshness and the last refresh outcome. Runs
@@ -209,10 +236,15 @@ export declare class AutoRAGAgent {
209
236
  */
210
237
  private scanMirrorStaleness;
211
238
  private exactDuplicateExclusions;
212
- syncBM25(): Promise<BM25SyncResult | undefined>;
213
239
  syncMinSync(): Promise<MinSyncSyncResult | undefined>;
214
240
  prepareJikji(): Promise<readonly AutoRAGJikjiPrepareResult[] | undefined>;
215
241
  private executeJikjiPrepare;
242
+ /**
243
+ * Start Jikji preparation without delaying the current search turn. A
244
+ * subsequent turn can use the prepared index, while the child process keeps
245
+ * running after emit_autorag_results has terminated the agent loop.
246
+ */
247
+ private scheduleJikjiPrepare;
216
248
  private sanitizeJikjiPrepareResult;
217
249
  /**
218
250
  * Provider method for the jikji_find tool. Runs JikjiClient.find over all
@@ -271,7 +303,7 @@ export declare class AutoRAGAgent {
271
303
  results: RetrievalResult[];
272
304
  diagnostics: RetrievalDiagnostic[];
273
305
  }>;
274
- /** The retrieval method registry (posix active; vector/bm25/hybrid pluggable). */
306
+ /** The retrieval method registry (posix, MinSync, and datasource methods). */
275
307
  getMethodRegistry(): RetrievalMethodRegistry;
276
308
  getSystemPrompt(): string;
277
309
  private rerankWithMemory;
@@ -17,7 +17,9 @@ export interface BashToolOptions {
17
17
  export interface BashToolDetails {
18
18
  readonly method: "bash";
19
19
  readonly command: string;
20
+ readonly aborted: boolean;
20
21
  readonly exitCode: number | undefined;
22
+ readonly terminationFailed: boolean;
21
23
  readonly timedOut: boolean;
22
24
  readonly truncated: boolean;
23
25
  }
@@ -7,6 +7,6 @@ export { createEmitResultsTool, EMIT_AUTORAG_RESULTS_TOOL_NAME } from "./emit-re
7
7
  export { createJikjiFindTool, JIKJI_FIND_TOOL_NAME, type JikjiFindDetails, type JikjiFindPerRootPolicy, type JikjiFindProvider, type JikjiFindProviderResult, type MergedJikjiPolicy, } from "./jikji-find-tool.js";
8
8
  export { createSearchAllDocumentsTool, SEARCH_ALL_DOCUMENTS_TOOL_NAME, type SearchAllDocumentsDetails, type SearchAllDocumentsProvider, type SearchAllDocumentsResult, } from "./search-all-tool.js";
9
9
  export { createSearchDatasourceDocumentsTool, type DatasourceSearchProvider, SEARCH_DATASOURCE_DOCUMENTS_TOOL_NAME, type SearchDatasourceDocumentsDetails, } from "./search-datasource-tool.js";
10
- export type { SearchDocumentDiagnostic, SearchDocumentDiagnosticCode, SearchDocumentDiagnosticSeverity, SearchDocumentEvidence, SearchDocumentResult, SearchDocumentsResponse, SearchDocumentWarning, } from "./search-documents.js";
10
+ export type { SearchDocumentDiagnostic, SearchDocumentDiagnosticCode, SearchDocumentDiagnosticSeverity, SearchDocumentEvidence, SearchDocumentResult, SearchDocumentsResponse, SearchDocumentsStreamEvent, SearchDocumentWarning, } from "./search-documents.js";
11
11
  export { createSearchMinSyncDocumentsTool, SEARCH_MINSYNC_DOCUMENTS_TOOL_NAME, type SearchMinSyncDocumentsDetails, } from "./search-minsync-tool.js";
12
12
  export { buildSystemPrompt, type SystemPromptConfig } from "./system-prompt.js";
@@ -9,12 +9,12 @@ export type SearchDocumentDiagnosticSeverity = "info" | "warning" | "error";
9
9
  * (#21/#22) does not require another source-breaking type change; codes not yet
10
10
  * emitted are still valid members.
11
11
  */
12
- export type SearchDocumentDiagnosticCode = "empty-query" | "unknown-warning" | "caller-tool-dropped" | "bm25-unavailable" | "bm25-degraded-fallback" | "minsync-unavailable" | "parser-skipped" | "parser-failed" | "duplicate-excluded" | "unsupported-file" | "stale-index" | "deleted-mirror" | "retrieval-method-failed" | "jikji-unavailable" | "jikji-prepare-failed" | "jikji-find-failed" | "refresh-failed" | "watch-failed" | "watch-limited";
12
+ export type SearchDocumentDiagnosticCode = "empty-query" | "unknown-warning" | "caller-tool-dropped" | "minsync-unavailable" | "parser-skipped" | "parser-failed" | "pdf-java-version" | "duplicate-excluded" | "unsupported-file" | "stale-index" | "deleted-mirror" | "pdf-extract-thin" | "pdf-hybrid-unavailable" | "retrieval-method-failed" | "jikji-unavailable" | "jikji-prepare-failed" | "jikji-find-failed" | "refresh-failed" | "watch-failed" | "watch-limited" | "unknown-datasource-skill";
13
13
  export interface SearchDocumentDiagnostic {
14
14
  readonly code: SearchDocumentDiagnosticCode;
15
15
  readonly severity: SearchDocumentDiagnosticSeverity;
16
16
  readonly message: string;
17
- /** Component label (e.g. "sanitizer", "bm25") or opaque virtual path — never a real filesystem path. */
17
+ /** Component label (e.g. "sanitizer", "minsync") or opaque virtual path — never a real filesystem path. */
18
18
  readonly source?: string;
19
19
  }
20
20
  export interface SearchDocumentEvidence {
@@ -43,6 +43,15 @@ export interface SearchDocumentsResponse {
43
43
  */
44
44
  readonly diagnostics?: readonly SearchDocumentDiagnostic[];
45
45
  }
46
+ export type SearchDocumentsStreamEvent = {
47
+ readonly type: "progress";
48
+ readonly sessionId: string;
49
+ readonly query: string;
50
+ readonly text: string;
51
+ } | {
52
+ readonly type: "complete";
53
+ readonly response: SearchDocumentsResponse;
54
+ };
46
55
  type SearchSessions = Map<string, {
47
56
  query: string;
48
57
  registry: Map<number, CuratedResult>;
@@ -51,7 +60,7 @@ type ReadonlySearchSessions = ReadonlyMap<string, {
51
60
  query: string;
52
61
  registry: ReadonlyMap<number, CuratedResult>;
53
62
  }>;
54
- export declare function createEmptySearchDocumentsResponse(sessionId: string, query: string, sessions: SearchSessions): SearchDocumentsResponse;
63
+ export declare function createEmptySearchDocumentsResponse(sessionId: string, query: string, sessions: SearchSessions, diagnostics?: readonly SearchDocumentDiagnostic[]): SearchDocumentsResponse;
55
64
  export declare function recordStructuredResultsSession(sessionId: string, query: string, details: AutoRAGResultsDetails, sessions: SearchSessions, memory: RetrievalMemory, componentDiagnostics?: readonly SearchDocumentDiagnostic[]): SearchDocumentsResponse;
56
65
  export declare function recordNumberedFeedback(sessions: ReadonlySearchSessions, memory: RetrievalMemory, sessionId: string, usefulNumbers: readonly number[], notUsefulNumbers: readonly number[]): void;
57
66
  export {};
@@ -1,14 +1,14 @@
1
1
  import type { AgentTool } from "@earendil-works/pi-agent-core";
2
2
  import { Type } from "typebox";
3
3
  import type { MinSyncVectorMethod } from "../minsync/method.js";
4
- export declare const SEARCH_MINSYNC_DOCUMENTS_TOOL_NAME = "search_minsync_documents";
4
+ export declare const SEARCH_MINSYNC_DOCUMENTS_TOOL_NAME = "semantic_search_local_docs";
5
5
  declare const searchMinSyncSchema: Type.TObject<{
6
6
  query: Type.TString;
7
7
  topK: Type.TOptional<Type.TInteger>;
8
8
  scope: Type.TOptional<Type.TString>;
9
9
  }>;
10
10
  export interface SearchMinSyncDocumentsDetails {
11
- readonly method: "search_minsync_documents";
11
+ readonly method: "semantic_search_local_docs";
12
12
  readonly resultCount: number;
13
13
  readonly sources: readonly string[];
14
14
  readonly available: boolean;
@@ -0,0 +1,3 @@
1
+ import type { CommandContext } from "./types.js";
2
+ /** Show the persisted source/chunk evidence attached to a search session. */
3
+ export declare function runEvidence(ctx: CommandContext): Promise<number>;
@@ -1,8 +1,8 @@
1
1
  import type { CommandContext } from "./types.js";
2
2
  /**
3
- * Run the `autorag index` command. `reset` removes the parsed mirror, BM25,
4
- * and MinSync index directories under `.autorag` (preserving `bin`,
5
- * `datasources`, and the memory file). `rebuild` resets then re-runs a forced
3
+ * Run the `autorag index` command. `reset` removes the parsed mirror
4
+ * and MinSync indexes
5
+ * (preserving `bin`, `datasources`, and the memory file). `rebuild` resets then re-runs a forced
6
6
  * refresh. Returns 0 on success, 2 for usage/decline errors, 1 for runtime
7
7
  * errors including path-escape guard violations.
8
8
  */
@@ -2,13 +2,12 @@ import { type RefreshMethod } from "../../agent/agent.js";
2
2
  import type { CommandContext } from "./types.js";
3
3
  /**
4
4
  * `autorag refresh` — parse configured search paths and resync every active
5
- * index (parsed mirror, BM25, MinSync, datasources, jikji). Model-free: no LLM
5
+ * index (parsed mirror, MinSync, datasources, jikji). Model-free: no LLM
6
6
  * is constructed. Output is rendered through the path-opaque refresh renderer;
7
7
  * the raw result (which carries absolute `indexPath`) is never printed.
8
8
  *
9
- * `--method bm25,minsync,parsed` restricts which methods run. When omitted, all
10
- * methods run. Parsed mirrors are always synced when BM25 or MinSync is selected
11
- * (they index over the parsed mirrors).
9
+ * `--method minsync,parsed` restricts which methods run. When omitted, all
10
+ * methods run. Parsed mirrors are always synced when MinSync is selected.
12
11
  */
13
12
  export declare function runRefresh(ctx: CommandContext): Promise<number>;
14
13
  /**
@@ -21,10 +21,10 @@ export declare function classifySearchHealthHint(error: unknown): SearchHealthHi
21
21
  /**
22
22
  * Search-command dependencies. Tests inject `agentFactory` to bypass real
23
23
  * model construction and AutoRAGAgent instantiation, returning a stub whose
24
- * `searchDocuments` resolves a canned {@link SearchDocumentsResponse}.
24
+ * `searchDocumentsStream` yields progress and a canned completion.
25
25
  */
26
26
  export interface SearchDeps {
27
- agentFactory?: (opts: AutoRAGAgentOptions) => Pick<AutoRAGAgent, "searchDocuments">;
27
+ agentFactory?: (opts: AutoRAGAgentOptions) => Pick<AutoRAGAgent, "searchDocumentsStream">;
28
28
  modelResolver?: (config: CliConfig) => ResolvedAgentModel;
29
29
  }
30
30
  /**
@@ -0,0 +1,36 @@
1
+ import type { AgentEvent } from "@earendil-works/pi-agent-core";
2
+ import { AutoRAGAgent, type AutoRAGAgentOptions } from "../../agent/agent.js";
3
+ import { type CliConfig, type ResolvedAgentModel } from "../config.js";
4
+ import { type TuiSessionRecord, type TuiSessionStore } from "../tui-session-store.js";
5
+ import type { CommandContext } from "./types.js";
6
+ export { parseSlashCommand } from "../tui-commands.js";
7
+ export interface TuiDriver {
8
+ submissions: string[];
9
+ rendered: string[];
10
+ started: boolean;
11
+ stopped: boolean;
12
+ onSubmit?: (text: string) => void | Promise<void>;
13
+ onExit?: () => void;
14
+ onEvent?: (event: AgentEvent) => void;
15
+ onInput?: (data: string) => void;
16
+ }
17
+ export interface TuiPresenter {
18
+ handle(event: AgentEvent): void;
19
+ lines(): readonly string[];
20
+ working(): boolean;
21
+ setWorking(active: boolean): void;
22
+ beginRun(): void;
23
+ interrupt(): void;
24
+ status(): string;
25
+ reset(): void;
26
+ }
27
+ type TuiAgent = Pick<AutoRAGAgent, "searchDocumentsStream"> & Partial<Pick<AutoRAGAgent, "subscribe" | "abort">>;
28
+ export interface TuiDeps {
29
+ agentFactory?: (opts?: AutoRAGAgentOptions) => TuiAgent;
30
+ modelResolver?: (config: CliConfig) => ResolvedAgentModel;
31
+ tuiFactory?: (ctx: CommandContext) => TuiDriver;
32
+ sessionStore?: TuiSessionStore;
33
+ }
34
+ export declare function createTuiPresenter(): TuiPresenter;
35
+ export declare function renderTuiSessionView(session: TuiSessionRecord | undefined, input: string): string;
36
+ export declare function runTui(ctx: CommandContext, deps?: TuiDeps): Promise<number>;
@@ -0,0 +1,13 @@
1
+ import { startUiServer, type UiServer } from "../../ui/server.js";
2
+ import type { CommandContext } from "./types.js";
3
+ export interface UiCommandDeps {
4
+ readonly startServer?: typeof startUiServer;
5
+ readonly openBrowser?: (url: string) => Promise<void>;
6
+ readonly waitUntilStopped?: (server: UiServer) => Promise<void>;
7
+ readonly createToken?: () => string;
8
+ }
9
+ /**
10
+ * `autorag ui` — local loopback dashboard for folders and datasource connections.
11
+ */
12
+ export declare function runUi(ctx: CommandContext, deps?: UiCommandDeps): Promise<number>;
13
+ export declare function openBrowser(url: string): Promise<void>;
@@ -1,6 +1,6 @@
1
1
  import type { CommandContext } from "./types.js";
2
2
  /**
3
- * `autorag watch` — keep parsed/BM25/MinSync indexes current.
3
+ * `autorag watch` — keep parsed/MinSync indexes current.
4
4
  *
5
5
  * - default: long-running recursive fs watch with debounced refresh
6
6
  * - `--once`: single refresh tick (for cron / launchd / Task Scheduler)