Getting started#
This guide installs @kontourai/forage, runs a first crawl, stores the pages
as snapshots, and replays them offline. For what each piece is, read
Concepts. For every option, read the Reference.
Requirements#
- Node.js 22 or newer (
engines.nodeis>=22). - The package is ESM only (
"type": "module"). - Rendering JavaScript pages is optional. It needs the
playwrightpeer dependency and its Chromium browser. Without them, forage keeps the plain HTTP snapshot and adds a warning.
Install#
npm install @kontourai/forage
Add playwright as well only if you want render: "on-shell" or
render: "always" to drive a real browser:
npm install playwright
Run a first crawl#
crawl(seed, policy) walks one site breadth-first and returns a manifest.
Egress is SSRF-guarded, robots.txt is honored, and only same-origin links are
followed, all by default.
import { crawl } from "@kontourai/forage";
const manifest = await crawl(
{
url: "https://example.com/",
// Identify your crawler. The default user agent carries a placeholder contact.
userAgent: "my-review-bot/1.0 (+https://example.org/bot; contact: ops@example.org)",
},
{ maxPages: 8, maxDepth: 1, discovery: "links", render: "never" },
);
for (const page of manifest.pages) {
console.log(page.depth, page.status, page.url);
console.log(" cite as:", page.sourceRef);
}
console.log("truncated:", manifest.truncated);
console.log(manifest.warnings);
crawl() throws only InvalidCrawlConfigError, for a bad seed or policy.
Every other failure, such as a denied address, a robots.txt refusal, a timeout,
or an HTTP error, becomes a string in manifest.warnings, and the crawl
continues with the next URL.
Keep snapshots and replay them#
Pass a store to keep every fetched page. A filesystem store writes one
directory per source under root:
import { crawl, createFilesystemSnapshotStore } from "@kontourai/forage";
const store = createFilesystemSnapshotStore({ root: "./snapshots" });
const seed = { url: "https://example.com/" };
// Live: fetch over the network and put each snapshot into the store.
const live = await crawl(seed, { maxPages: 8, store });
// Replay: no network. Each page is served from the store's latest snapshot.
const replayed = await crawl(seed, { maxPages: 8, store, mode: "replay" });
Replay looks pages up by an id built from the seed URL and the page URL, so
replay with the same seed URL you crawled. mode: "replay" without a store
is an InvalidCrawlConfigError.
Resolve a citation later#
A sourceRef names one exact snapshot. Resolve it against the store, offline:
import { resolveSnapshotSourceRef } from "@kontourai/forage/fetch";
// `store` and `live` are from the previous example.
const replay = await resolveSnapshotSourceRef(store, live.pages[0]!.sourceRef);
if (!replay.ok) throw new Error(`${replay.error.kind}: ${replay.error.message}`);
console.log(replay.snapshot.body);
Fetch a single page#
For one URL without a frontier, use fetchSource() from the ./fetch subpath.
It returns { snapshot?, error?, warnings? } and never throws for operational
failures:
import { fetchSource } from "@kontourai/forage/fetch";
const result = await fetchSource(
{ id: "pricing-page", url: "https://example.com/pricing", egress: { guarded: true } },
{ maxResponseBytes: 8 * 1024 * 1024 },
);
if (result.error) console.error(result.error.kind, result.error.message);
else console.log(result.snapshot!.bodyHash);
fetchSource() reads a store you pass in options.store to revalidate, but it
never writes to it. Call store.put(snapshot) yourself if you want to keep the
result.
Crawl a local test server#
The guard rejects loopback addresses and non-default ports, so a crawl of
http://127.0.0.1:4173/ fails with egress-denied: ... (INVALID_PORT). For
tests, allow that exact origin, and never a production hostname:
const manifest = await crawl(
{ url: "http://127.0.0.1:4173/" },
{
maxPages: 1,
egress: { guarded: true, testOnlyAllowedLoopbackOrigins: ["http://127.0.0.1:4173"] },
},
);
Each entry must be an exact http://127.0.0.1:<port> or http://[::1]:<port>
origin with an explicit port.
Next steps#
- Concepts: the frontier, guarded egress, snapshots, and replay.
- Reference: every export, option, default, and limit.
- Troubleshooting: what each warning and error means.