@bpinternal/site-scout 0.1.0
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 +92 -0
- package/dist/index.cjs +1797 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +211 -0
- package/dist/index.d.ts +211 -0
- package/dist/index.js +1762 -0
- package/dist/index.js.map +1 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# @bpinternal/site-scout
|
|
2
|
+
|
|
3
|
+
Website URL discovery + LLM page selection — scouts a site for the pages worth
|
|
4
|
+
reading. Given a website, it finds candidate URLs, scores them into a
|
|
5
|
+
prioritized tree, then makes **one** injected-LLM call to pick the pages best
|
|
6
|
+
suited to seed a support bot's knowledge base.
|
|
7
|
+
|
|
8
|
+
## What it does
|
|
9
|
+
|
|
10
|
+
Two independent units, composed:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
discover(website, opts, deps) -> MasterMap (prioritized tree)
|
|
14
|
+
select(map, opts, deps) -> Selection (best ≤limit URLs)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`findKnowledgeUrls()` runs guard → discover → select for the common case.
|
|
18
|
+
|
|
19
|
+
- **Discovery** pulls candidate URLs from **5 sources** — `llms.txt` /
|
|
20
|
+
`llms-full.txt`, `sitemap.xml` (incl. children), `robots.txt`, the host's
|
|
21
|
+
`discoverUrls` crawl, and a `site:` web search — normalizes/dedupes them, and
|
|
22
|
+
builds a **score tree**: one node per path segment, with aggregate scores and
|
|
23
|
+
leaf counts so selection can reason about cardinality without walking every
|
|
24
|
+
leaf.
|
|
25
|
+
- **Selection** renders the tree as a compact numbered list and makes **one
|
|
26
|
+
LLM call** (`extract`) that returns the picked page _numbers_ (best-first),
|
|
27
|
+
which map back to URLs. The LLM pick is the decision — no deterministic
|
|
28
|
+
fallback list.
|
|
29
|
+
|
|
30
|
+
## The injection contract (`SiteScoutDeps`)
|
|
31
|
+
|
|
32
|
+
site-scout does no I/O and never imports a runtime itself. Every consumer
|
|
33
|
+
provides the I/O + LLM as `deps`:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
type SiteScoutDeps = {
|
|
37
|
+
// discovery I/O
|
|
38
|
+
fetchText(url, baseHost, timeoutMs?): Promise<string | null>
|
|
39
|
+
fetchJson(url, baseHost, timeoutMs?): Promise<T | null>
|
|
40
|
+
discoverUrls(args): Promise<{ urls: string[]; stopReason: string }>
|
|
41
|
+
webSearch(args): Promise<{ results: SearchResult[] }>
|
|
42
|
+
// the one LLM touchpoint (zai-shaped extract)
|
|
43
|
+
extract(input, schema, { instructions }): Promise<z.infer<schema>>
|
|
44
|
+
// optional record/replay cache around the LLM call (defaults to passthrough)
|
|
45
|
+
cache?(kind, keyParts, fn): Promise<T>
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`@bpinternal/zui` is a **peer dependency** — consumers bring their own
|
|
50
|
+
(the schema passed to `extract` uses it).
|
|
51
|
+
|
|
52
|
+
## Usage
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import { findKnowledgeUrls } from '@bpinternal/site-scout'
|
|
56
|
+
|
|
57
|
+
const result = await findKnowledgeUrls(
|
|
58
|
+
{
|
|
59
|
+
website: 'https://example.com',
|
|
60
|
+
limit: 50,
|
|
61
|
+
topics: ['pricing', 'docs'],
|
|
62
|
+
prompt: 'Prioritize developer docs, API reference, pricing.',
|
|
63
|
+
context: { company: 'Example', website: 'https://example.com', overview: '…' },
|
|
64
|
+
},
|
|
65
|
+
deps // your SiteScoutDeps implementation
|
|
66
|
+
)
|
|
67
|
+
// result.urls — prioritized URLs, best first
|
|
68
|
+
// result.discovered — total distinct URLs found before selection
|
|
69
|
+
// result.stopReason — 'ok' | 'unsupported_site' | 'no_sources' | 'time_limit_reached'
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`discover()` and `select()` are also exported for callers that want to show the
|
|
73
|
+
tree, let a user refine, then select. `fetchText`/`fetchJson`/`hostOf`/
|
|
74
|
+
`siteOrigin` (an SSRF-guarded fetcher + URL helpers) are re-exported so a
|
|
75
|
+
consumer can reuse them when wiring its own deps.
|
|
76
|
+
|
|
77
|
+
## Record / replay fixtures
|
|
78
|
+
|
|
79
|
+
The tests are offline and deterministic:
|
|
80
|
+
|
|
81
|
+
- **Network** — discovery I/O is wrapped by `cachedDeps(group, realDeps)`, which
|
|
82
|
+
records/replays each call against `src/__fixtures__/<group>.jsonl` (one file
|
|
83
|
+
per site). In replay mode a miss throws (a test can't silently hit the
|
|
84
|
+
network); in production `cachedDeps` is a passthrough. Mode is env-driven
|
|
85
|
+
(`URL_FIXTURE_RECORD` to record; `NODE_ENV=test` / `VITEST` / `BUN_TEST` /
|
|
86
|
+
`URL_FIXTURE_REPLAY` to replay).
|
|
87
|
+
- **LLM** — the `select` call is keyed and replayed from
|
|
88
|
+
`src/__fixtures__/llm-cache.jsonl`. The package e2e (`index.e2e.test.ts`)
|
|
89
|
+
loads that file and serves the recorded decision, so no live model is called.
|
|
90
|
+
|
|
91
|
+
Fixtures are **recorded via the app harness** (viber-compiler-bot), which owns
|
|
92
|
+
the real runtime/LLM wiring; this package only replays them.
|