@wrenautomation/read 0.0.0-stage → 0.30.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.
Files changed (72) hide show
  1. package/LICENSE.md +61 -0
  2. package/README.md +36 -2
  3. package/dist/fetch/fetcher.d.ts +84 -0
  4. package/dist/fetch/fetcher.js +180 -0
  5. package/dist/fetch/fetcher.js.map +1 -0
  6. package/dist/fetch/index.d.ts +9 -0
  7. package/dist/fetch/index.js +10 -0
  8. package/dist/fetch/index.js.map +1 -0
  9. package/dist/fetch/ladder.d.ts +44 -0
  10. package/dist/fetch/ladder.js +18 -0
  11. package/dist/fetch/ladder.js.map +1 -0
  12. package/dist/fetch/robots.d.ts +14 -0
  13. package/dist/fetch/robots.js +92 -0
  14. package/dist/fetch/robots.js.map +1 -0
  15. package/dist/index.d.ts +11 -0
  16. package/dist/index.js +12 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/study/claims.d.ts +19 -0
  19. package/dist/study/claims.js +13 -0
  20. package/dist/study/claims.js.map +1 -0
  21. package/dist/study/drafts.d.ts +43 -0
  22. package/dist/study/drafts.js +93 -0
  23. package/dist/study/drafts.js.map +1 -0
  24. package/dist/study/evidence.d.ts +17 -0
  25. package/dist/study/evidence.js +72 -0
  26. package/dist/study/evidence.js.map +1 -0
  27. package/dist/study/grounding.d.ts +29 -0
  28. package/dist/study/grounding.js +58 -0
  29. package/dist/study/grounding.js.map +1 -0
  30. package/dist/study/index.d.ts +11 -0
  31. package/dist/study/index.js +12 -0
  32. package/dist/study/index.js.map +1 -0
  33. package/dist/study/report.d.ts +55 -0
  34. package/dist/study/report.js +56 -0
  35. package/dist/study/report.js.map +1 -0
  36. package/dist/text/entities.d.ts +2 -0
  37. package/dist/text/entities.js +4 -0
  38. package/dist/text/entities.js.map +1 -0
  39. package/dist/text/htmltext.d.ts +30 -0
  40. package/dist/text/htmltext.js +208 -0
  41. package/dist/text/htmltext.js.map +1 -0
  42. package/dist/text/index.d.ts +7 -0
  43. package/dist/text/index.js +8 -0
  44. package/dist/text/index.js.map +1 -0
  45. package/dist/text/text.d.ts +8 -0
  46. package/dist/text/text.js +38 -0
  47. package/dist/text/text.js.map +1 -0
  48. package/dist/video/gemini.d.ts +46 -0
  49. package/dist/video/gemini.js +93 -0
  50. package/dist/video/gemini.js.map +1 -0
  51. package/dist/video/index.d.ts +39 -0
  52. package/dist/video/index.js +16 -0
  53. package/dist/video/index.js.map +1 -0
  54. package/dist/video/list.d.ts +35 -0
  55. package/dist/video/list.js +32 -0
  56. package/dist/video/list.js.map +1 -0
  57. package/dist/video/read.d.ts +53 -0
  58. package/dist/video/read.js +92 -0
  59. package/dist/video/read.js.map +1 -0
  60. package/dist/video/shots.d.ts +35 -0
  61. package/dist/video/shots.js +139 -0
  62. package/dist/video/shots.js.map +1 -0
  63. package/dist/video/tools.d.ts +16 -0
  64. package/dist/video/tools.js +29 -0
  65. package/dist/video/tools.js.map +1 -0
  66. package/dist/video/url.d.ts +73 -0
  67. package/dist/video/url.js +255 -0
  68. package/dist/video/url.js.map +1 -0
  69. package/dist/video/watch.d.ts +94 -0
  70. package/dist/video/watch.js +321 -0
  71. package/dist/video/watch.js.map +1 -0
  72. package/package.json +55 -3
package/LICENSE.md ADDED
@@ -0,0 +1,61 @@
1
+ # PolyForm Strict License 1.0.0
2
+
3
+ <https://polyformproject.org/licenses/strict/1.0.0>
4
+
5
+ ## Acceptance
6
+
7
+ In order to get any license under these terms, you must agree to them as both strict obligations and conditions to all your licenses.
8
+
9
+ ## Copyright License
10
+
11
+ The licensor grants you a copyright license for the software to do everything you might do with the software that would otherwise infringe the licensor's copyright in it for any permitted purpose, other than distributing the software or making changes or new works based on the software.
12
+
13
+ ## Patent License
14
+
15
+ The licensor grants you a patent license for the software that covers patent claims the licensor can license, or becomes able to license, that you would infringe by using the software.
16
+
17
+ ## Noncommercial Purposes
18
+
19
+ Any noncommercial purpose is a permitted purpose.
20
+
21
+ ## Personal Uses
22
+
23
+ Personal use for research, experiment, and testing for the benefit of public knowledge, personal study, private entertainment, hobby projects, amateur pursuits, or religious observance, without any anticipated commercial application, is use for a permitted purpose.
24
+
25
+ ## Noncommercial Organizations
26
+
27
+ Use by any charitable organization, educational institution, public research organization, public safety or health organization, environmental protection organization, or government institution is use for a permitted purpose regardless of the source of funding or obligations resulting from the funding.
28
+
29
+ ## Fair Use
30
+
31
+ You may have "fair use" rights for the software under the law. These terms do not limit them.
32
+
33
+ ## No Other Rights
34
+
35
+ These terms do not allow you to sublicense or transfer any of your licenses to anyone else, or prevent the licensor from granting licenses to anyone else. These terms do not imply any other licenses.
36
+
37
+ ## Patent Defense
38
+
39
+ If you make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
40
+
41
+ ## Violations
42
+
43
+ The first time you are notified in writing that you have violated any of these terms, or done anything with the software not covered by your licenses, your licenses can nonetheless continue if you come into full compliance with these terms, and take practical steps to correct past violations, within 32 days of receiving notice. Otherwise, all your licenses end immediately.
44
+
45
+ ## No Liability
46
+
47
+ ***As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.***
48
+
49
+ ## Definitions
50
+
51
+ The **licensor** is the individual or entity offering these terms, and the **software** is the software the licensor makes available under these terms.
52
+
53
+ **You** refers to the individual or entity agreeing to these terms.
54
+
55
+ **Your company** is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. **Control** means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
56
+
57
+ **Your licenses** are all the licenses granted to you for the software under these terms.
58
+
59
+ **Use** means anything you do with the software requiring one of your licenses.
60
+
61
+ Required Notice: Copyright 2026 William Jin (https://wrenautomation.com)
package/README.md CHANGED
@@ -1,3 +1,37 @@
1
- # Temporary Holding Version
1
+ # read
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The web and files in, text out. No database, no accounts, no business rules: a library that
4
+ fetches and reads. Until 0.29.0 this was falcon (files, markup, media) and the fetch and video
5
+ halves of weaver; the research engine on top is [`@wrenautomation/birdwatch`](https://github.com/wrenautomation/engine/tree/main/packages/birdwatch),
6
+ whose `birdwatch` CLI carries these readers (`fetch`, `read`, `list`, `text`, `page`, `watch`).
7
+
8
+ | Import | Does |
9
+ |---|---|
10
+ | `@wrenautomation/read/fetch` | `PoliteFetcher` (one request at a time per host, a contact in the User-Agent, retries on 429 and 5xx, a byte cap), `robots` in warn mode, `fetchLadder` (plain GET up to a browser through a `LadderClient`: autobrowse's `web GET /fetch`) |
11
+ | `@wrenautomation/read/text` | `htmlText`, `pdfText`, `decodeHtml`, `decodeEntities`; `readPage` (HTML to title, text, links, mailtos, tels) |
12
+ | `@wrenautomation/read/video` | `watchFile` a video file: ffmpeg cuts and frames, an ear for the speech (`cohereSpeech`), an eye for the frames (`cohereEye`), and a video model's read of the whole file when given (`geminiVideo`, or `geminiReader` with no key check, for a proxy that holds the keys); shot labels. Over a URL: `readYoutube` (captions and chapters by yt-dlp, plus a video model's read of the screen in clips), `readYoutubeByUrl` (the video model alone, no download), `readVideo` (any other video: yt-dlp, then `watchFile`), `readAudio` (a podcast by its audio URL, then `hearAudio`), `watch` over a URL or a file, `flatPlaylist` (what a profile, channel or search holds), `download`. Every reader returns the one `VideoRead` shape |
13
+ | `@wrenautomation/read/study` | a study's synthesis reader: the quote gate (`pageQuoted`, `numbersQuoted`), the passages a page is shown by, drafts that cite claims, the markdown report |
14
+
15
+ Everything is a function of what you pass: a URL, bytes, text, a file path, models, tools. What
16
+ searches, stores, schedules or knows a customer belongs to the caller. Every model call goes
17
+ through `@wrenautomation/models`. `watch` and the readers over a URL need ffmpeg and yt-dlp.
18
+
19
+ ```ts
20
+ import { PoliteFetcher, userAgent } from "@wrenautomation/read/fetch";
21
+ import { readPage } from "@wrenautomation/read/text";
22
+
23
+ const fetcher = new PoliteFetcher(userAgent("you@example.com"));
24
+ const res = await fetcher.get(url);
25
+ const page = readPage(res.text, res.url);
26
+ ```
27
+
28
+ ## Develop
29
+
30
+ ```sh
31
+ pnpm install
32
+ pnpm lint && pnpm typecheck && pnpm test
33
+ ```
34
+
35
+ A `v*` tag publishes to npm (`.github/workflows/release.yml`, trusted publishing: no stored token).
36
+
37
+ License: PolyForm Strict 1.0.0 (`LICENSE.md`).
@@ -0,0 +1,84 @@
1
+ /**
2
+ * A fetch that failed after retries. `status` is the last HTTP status when the
3
+ * server answered (5xx after retries), null for transport failures: robots handling
4
+ * needs the difference (a 503 robots.txt means disallow-all; a dead host means the
5
+ * web's allow default).
6
+ */
7
+ export declare class FetchError extends Error {
8
+ name: string;
9
+ readonly status: number | null;
10
+ constructor(message: string, status?: number | null);
11
+ }
12
+ /** The declared UA, or a loud failure: contact info is an explicit operator choice, never invented. */
13
+ export declare function userAgent(contact: string | null | undefined): string;
14
+ /** What one GET produced. `url` is the final URL after redirects. */
15
+ export interface FetchResponse {
16
+ status: number;
17
+ url: string;
18
+ text: string;
19
+ /** The `fetch_tier` the page was kept under, when it was not the caller's own plain fetch. */
20
+ tier?: string;
21
+ }
22
+ /** The seam crawlers and gates depend on; tests substitute canned pages. */
23
+ export interface Fetcher {
24
+ readonly userAgent: string;
25
+ get(url: string): Promise<FetchResponse>;
26
+ }
27
+ export interface DownloadResult {
28
+ path: string;
29
+ /** false: already on disk, not re-fetched. */
30
+ downloaded: boolean;
31
+ size: number;
32
+ }
33
+ interface PoliteFetcherOptions {
34
+ /** Seconds between requests to one host. */
35
+ minInterval?: number;
36
+ /** Extra uniform(lo, hi) seconds per request so crawls don't tick like a metronome. */
37
+ jitter?: readonly [number, number] | null;
38
+ timeout?: number;
39
+ retries?: number;
40
+ /** Test seams. */
41
+ fetch?: typeof globalThis.fetch;
42
+ sleep?: (ms: number) => Promise<void>;
43
+ now?: () => number;
44
+ random?: () => number;
45
+ }
46
+ /**
47
+ * fetch() wrapper enforcing UA + throttle + retry in one place. The throttle is per
48
+ * host: a crawl across many small sites is not serialized globally.
49
+ */
50
+ export declare class PoliteFetcher implements Fetcher {
51
+ readonly userAgent: string;
52
+ private readonly minIntervalMs;
53
+ private readonly jitter;
54
+ private readonly timeoutMs;
55
+ private readonly retries;
56
+ private readonly fetchImpl;
57
+ private readonly sleep;
58
+ private readonly now;
59
+ private readonly random;
60
+ private readonly lastRequest;
61
+ /** Requests to one host are serialized so concurrent callers still honor the interval. */
62
+ private readonly hostQueues;
63
+ constructor(userAgent: string, opts?: PoliteFetcherOptions);
64
+ private throttle;
65
+ private request;
66
+ /**
67
+ * One throttled request as the raw `Response`, for a body the caller streams (a file it
68
+ * filters as it lands) or a method other than GET. `init` wins: its signal replaces the
69
+ * fetcher's timeout, its headers join the User-Agent. No retries: a half-read stream cannot be
70
+ * asked again, so the caller decides what a bad status means.
71
+ */
72
+ open(url: string, init?: RequestInit): Promise<Response>;
73
+ /**
74
+ * GET with throttle and bounded retries. 4xx returns (the caller interprets: a
75
+ * crawl 404 is data); transport errors and 5xx retry with backoff, then FetchError.
76
+ */
77
+ get(url: string): Promise<FetchResponse>;
78
+ /**
79
+ * Stream a (possibly large) file to `dest`; skip if already present. Writes to a
80
+ * .part file and renames, so an interrupted download never masquerades as complete.
81
+ */
82
+ download(url: string, dest: string): Promise<DownloadResult>;
83
+ }
84
+ export {};
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Polite HTTP fetching, shared by every layer that touches the network: the
3
+ * discovery ownership gate, the site crawler, bulk downloads. One class holds the
4
+ * etiquette (declared User-Agent with contact info, per-host throttle, bounded
5
+ * retries, never re-downloading what is on disk) so it cannot drift between callers.
6
+ */
7
+ import { createWriteStream } from "node:fs";
8
+ import { mkdir, rename, stat, unlink } from "node:fs/promises";
9
+ import { dirname } from "node:path";
10
+ import { Readable } from "node:stream";
11
+ import { pipeline } from "node:stream/promises";
12
+ const DEFAULT_RETRIES = 3;
13
+ const BACKOFF_MS = 2000;
14
+ /**
15
+ * A fetch that failed after retries. `status` is the last HTTP status when the
16
+ * server answered (5xx after retries), null for transport failures: robots handling
17
+ * needs the difference (a 503 robots.txt means disallow-all; a dead host means the
18
+ * web's allow default).
19
+ */
20
+ export class FetchError extends Error {
21
+ name = "FetchError";
22
+ status;
23
+ constructor(message, status = null) {
24
+ super(message);
25
+ this.status = status;
26
+ }
27
+ }
28
+ /** The declared UA, or a loud failure: contact info is an explicit operator choice, never invented. */
29
+ export function userAgent(contact) {
30
+ if (!contact) {
31
+ throw new FetchError("outbound fetches need a contact (an email address or URL a remote operator could reach you at)");
32
+ }
33
+ return `wrenautomation/0.1 (${contact})`;
34
+ }
35
+ const defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
36
+ /**
37
+ * fetch() wrapper enforcing UA + throttle + retry in one place. The throttle is per
38
+ * host: a crawl across many small sites is not serialized globally.
39
+ */
40
+ export class PoliteFetcher {
41
+ userAgent;
42
+ minIntervalMs;
43
+ jitter;
44
+ timeoutMs;
45
+ retries;
46
+ fetchImpl;
47
+ sleep;
48
+ now;
49
+ random;
50
+ lastRequest = new Map();
51
+ /** Requests to one host are serialized so concurrent callers still honor the interval. */
52
+ hostQueues = new Map();
53
+ constructor(userAgent, opts = {}) {
54
+ this.userAgent = userAgent;
55
+ this.minIntervalMs = (opts.minInterval ?? 1.0) * 1000;
56
+ this.jitter = opts.jitter ?? null;
57
+ this.timeoutMs = (opts.timeout ?? 30) * 1000;
58
+ this.retries = Math.max(1, opts.retries ?? DEFAULT_RETRIES);
59
+ this.fetchImpl = opts.fetch ?? globalThis.fetch;
60
+ this.sleep = opts.sleep ?? defaultSleep;
61
+ this.now = opts.now ?? (() => performance.now());
62
+ this.random = opts.random ?? Math.random;
63
+ }
64
+ async throttle(url) {
65
+ let host = "";
66
+ try {
67
+ host = new URL(url).host;
68
+ }
69
+ catch {
70
+ host = "";
71
+ }
72
+ const previous = this.hostQueues.get(host) ?? Promise.resolve();
73
+ const turn = previous.then(async () => {
74
+ let wait = this.minIntervalMs;
75
+ if (this.jitter) {
76
+ const [lo, hi] = this.jitter;
77
+ wait += (lo + (hi - lo) * this.random()) * 1000;
78
+ }
79
+ const elapsed = this.now() - (this.lastRequest.get(host) ?? Number.NEGATIVE_INFINITY);
80
+ if (elapsed < wait)
81
+ await this.sleep(wait - elapsed);
82
+ this.lastRequest.set(host, this.now());
83
+ });
84
+ this.hostQueues.set(host, turn.catch(() => { }));
85
+ await turn;
86
+ }
87
+ request(url, init = {}) {
88
+ const headers = new Headers(init.headers);
89
+ if (!headers.has("user-agent"))
90
+ headers.set("User-Agent", this.userAgent);
91
+ return this.fetchImpl(url, {
92
+ redirect: "follow",
93
+ signal: AbortSignal.timeout(this.timeoutMs),
94
+ ...init,
95
+ headers,
96
+ });
97
+ }
98
+ /**
99
+ * One throttled request as the raw `Response`, for a body the caller streams (a file it
100
+ * filters as it lands) or a method other than GET. `init` wins: its signal replaces the
101
+ * fetcher's timeout, its headers join the User-Agent. No retries: a half-read stream cannot be
102
+ * asked again, so the caller decides what a bad status means.
103
+ */
104
+ async open(url, init = {}) {
105
+ await this.throttle(url);
106
+ return this.request(url, init);
107
+ }
108
+ /**
109
+ * GET with throttle and bounded retries. 4xx returns (the caller interprets: a
110
+ * crawl 404 is data); transport errors and 5xx retry with backoff, then FetchError.
111
+ */
112
+ async get(url) {
113
+ let lastError = "";
114
+ let lastStatus = null;
115
+ for (let attempt = 0; attempt < this.retries; attempt++) {
116
+ await this.throttle(url);
117
+ try {
118
+ const resp = await this.request(url);
119
+ if (resp.status < 500) {
120
+ return { status: resp.status, url: resp.url || url, text: await resp.text() };
121
+ }
122
+ lastError = `HTTP ${resp.status}`;
123
+ lastStatus = resp.status;
124
+ await resp.body?.cancel().catch(() => { });
125
+ }
126
+ catch (err) {
127
+ lastError = describe(err);
128
+ lastStatus = null;
129
+ }
130
+ if (attempt < this.retries - 1)
131
+ await this.sleep(BACKOFF_MS * (attempt + 1));
132
+ }
133
+ throw new FetchError(`GET ${url} failed after ${this.retries} attempts: ${lastError}`, lastStatus);
134
+ }
135
+ /**
136
+ * Stream a (possibly large) file to `dest`; skip if already present. Writes to a
137
+ * .part file and renames, so an interrupted download never masquerades as complete.
138
+ */
139
+ async download(url, dest) {
140
+ const existing = await stat(dest).catch(() => null);
141
+ if (existing)
142
+ return { path: dest, downloaded: false, size: existing.size };
143
+ await mkdir(dirname(dest), { recursive: true });
144
+ const tmp = `${dest}.part`;
145
+ let lastError = "";
146
+ for (let attempt = 0; attempt < this.retries; attempt++) {
147
+ await this.throttle(url);
148
+ try {
149
+ const resp = await this.request(url);
150
+ if (resp.status >= 400) {
151
+ lastError = `HTTP ${resp.status}`;
152
+ await resp.body?.cancel().catch(() => { });
153
+ if (resp.status < 500)
154
+ break; // 4xx will not improve on retry
155
+ continue;
156
+ }
157
+ const body = resp.body ? Readable.fromWeb(resp.body) : Readable.from([]);
158
+ await pipeline(body, createWriteStream(tmp));
159
+ await rename(tmp, dest);
160
+ const size = (await stat(dest)).size;
161
+ return { path: dest, downloaded: true, size };
162
+ }
163
+ catch (err) {
164
+ lastError = describe(err);
165
+ if (attempt < this.retries - 1)
166
+ await this.sleep(BACKOFF_MS * (attempt + 1));
167
+ }
168
+ }
169
+ await unlink(tmp).catch(() => { });
170
+ throw new FetchError(`download ${url} failed: ${lastError}`);
171
+ }
172
+ }
173
+ function describe(err) {
174
+ if (err instanceof Error) {
175
+ const cause = err.cause instanceof Error ? `: ${err.cause.message}` : "";
176
+ return `${err.name}: ${err.message}${cause}`;
177
+ }
178
+ return String(err);
179
+ }
180
+ //# sourceMappingURL=fetcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetcher.js","sourceRoot":"","sources":["../../src/fetch/fetcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The open web by HTTP: a polite fetcher (one request at a time per host, a real User-Agent with a
3
+ * contact, retries on 429 and 5xx, a byte cap), robots.txt in warn mode, and the fetch ladder
4
+ * that climbs from a plain GET to a browser (`LadderClient`: autobrowse's `web GET /fetch`).
5
+ * Turning the page into text is `./text`'s. No database here.
6
+ */
7
+ export * from "./fetcher.js";
8
+ export * from "./ladder.js";
9
+ export * from "./robots.js";
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The open web by HTTP: a polite fetcher (one request at a time per host, a real User-Agent with a
3
+ * contact, retries on 429 and 5xx, a byte cap), robots.txt in warn mode, and the fetch ladder
4
+ * that climbs from a plain GET to a browser (`LadderClient`: autobrowse's `web GET /fetch`).
5
+ * Turning the page into text is `./text`'s. No database here.
6
+ */
7
+ export * from "./fetcher.js";
8
+ export * from "./ladder.js";
9
+ export * from "./robots.js";
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/fetch/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * The client of autobrowse's fetch ladder, `web GET /fetch`. A caller asks it for a blocked
3
+ * page; autobrowse climbs `impersonate`, `render` and `exit`. A block is data in the answer.
4
+ * A failed call is the other result.
5
+ */
6
+ /** The rungs a ladder climbs, lowest first. */
7
+ export declare const LADDER_TIERS: readonly ["impersonate", "render", "exit"];
8
+ export type LadderTier = (typeof LADDER_TIERS)[number];
9
+ export declare const isLadderTier: (tier: string | null | undefined) => tier is LadderTier;
10
+ /** Whoever answers `web GET /fetch`: autobrowse's site client, or a test double. */
11
+ export interface LadderClient {
12
+ /** `account`: the credential the call runs as (`reddit@alt`); the worker's default when absent. */
13
+ call<T>(site: string, method: string, path: string, input?: unknown, o?: {
14
+ account?: string;
15
+ }): Promise<T>;
16
+ }
17
+ /** One rung the ladder tried, and what it met there. */
18
+ export interface FetchStep {
19
+ tier: string;
20
+ status: number | null;
21
+ block: string | null;
22
+ }
23
+ /** What `web GET /fetch` answers (`Fetched`): the fields this side reads. */
24
+ interface Fetched {
25
+ finalUrl: string;
26
+ status: number | null;
27
+ html: string;
28
+ /** The content-type the page answered with, when the worker says. */
29
+ type?: string | null;
30
+ /** The rung that answered: the last one tried. */
31
+ tier: LadderTier;
32
+ /** null: a usable page; else the signal the last rung still hit. */
33
+ block: string | null;
34
+ tries: FetchStep[];
35
+ }
36
+ /** `cap`: the day's `fetch` bucket is spent (429). `offline`: any other failed call. */
37
+ type LadderFailure = "cap" | "offline";
38
+ /** One `web GET /fetch`. A failed call is a result, never a throw. */
39
+ export declare function fetchLadder(sites: LadderClient, url: string, from: LadderTier): Promise<{
40
+ fetched: Fetched;
41
+ } | {
42
+ failed: LadderFailure;
43
+ }>;
44
+ export {};
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The client of autobrowse's fetch ladder, `web GET /fetch`. A caller asks it for a blocked
3
+ * page; autobrowse climbs `impersonate`, `render` and `exit`. A block is data in the answer.
4
+ * A failed call is the other result.
5
+ */
6
+ /** The rungs a ladder climbs, lowest first. */
7
+ export const LADDER_TIERS = ["impersonate", "render", "exit"];
8
+ export const isLadderTier = (tier) => LADDER_TIERS.some((t) => t === tier);
9
+ /** One `web GET /fetch`. A failed call is a result, never a throw. */
10
+ export async function fetchLadder(sites, url, from) {
11
+ try {
12
+ return { fetched: await sites.call("web", "GET", "/fetch", { url, from }) };
13
+ }
14
+ catch (err) {
15
+ return { failed: err.status === 429 ? "cap" : "offline" };
16
+ }
17
+ }
18
+ //# sourceMappingURL=ladder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ladder.js","sourceRoot":"","sources":["../../src/fetch/ladder.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,+CAA+C;AAC/C,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,aAAa,EAAE,QAAQ,EAAE,MAAM,CAAU,CAAC;AAEvE,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,IAA+B,EAAsB,EAAE,CAClF,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;AAsCvC,sEAAsE;AACtE,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,KAAmB,EACnB,GAAW,EACX,IAAgB;IAEhB,IAAI,CAAC;QACH,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC,IAAI,CAAU,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IACvF,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,EAAE,MAAM,EAAG,GAA4B,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;IACtF,CAAC;AACH,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { type Fetcher } from "./fetcher.js";
2
+ export declare class RobotsPolicy {
3
+ private readonly robot;
4
+ private readonly disallowAll;
5
+ private constructor();
6
+ static allowAll(): RobotsPolicy;
7
+ static disallowAll(): RobotsPolicy;
8
+ static parse(text: string): RobotsPolicy;
9
+ canFetch(userAgent: string, url: string): boolean;
10
+ }
11
+ export type RobotsCache = Map<string, RobotsPolicy>;
12
+ /** The robots policy for this url's scheme+host, fetched once each. */
13
+ export declare function robotsFor(fetcher: Fetcher, url: string, cache: RobotsCache): Promise<RobotsPolicy>;
14
+ export declare function canFetch(fetcher: Fetcher, url: string, cache: RobotsCache): Promise<boolean>;
@@ -0,0 +1,92 @@
1
+ /**
2
+ * robots.txt reading, shared by every page-fetching consumer.
3
+ *
4
+ * The policy that binds a fetch is the one on the HOST actually being fetched, over
5
+ * the SCHEME actually in use: apex and www can serve different files, so policies
6
+ * are cached per scheme+host and fetched over the URL's own scheme.
7
+ *
8
+ * Absence semantics follow RFC 9309: a missing robots.txt (404, or a dead host)
9
+ * means everything is allowed, but a server that ANSWERED 5xx is saying
10
+ * "unavailable", which means full disallow.
11
+ *
12
+ * Matching is robots-parser's RFC 9309 reading: the group for our product token
13
+ * (groups naming it merged), else `*`; the longest matching rule wins, Allow on a
14
+ * tie; `*` and `$` in paths.
15
+ */
16
+ import parseRobots from "robots-parser";
17
+ import { FetchError } from "./fetcher.js";
18
+ /** robots-parser checks a URL's origin against the file's; ours are read per origin, so one stand-in. */
19
+ const ORIGIN = "https://robots.invalid";
20
+ // A CommonJS module: at runtime the default import is the function its typings
21
+ // declare, which NodeNext resolution types as a namespace instead.
22
+ const robotsParser = parseRobots;
23
+ export class RobotsPolicy {
24
+ robot;
25
+ disallowAll;
26
+ constructor(robot, disallowAll) {
27
+ this.robot = robot;
28
+ this.disallowAll = disallowAll;
29
+ }
30
+ static allowAll() {
31
+ return new RobotsPolicy(null, false);
32
+ }
33
+ static disallowAll() {
34
+ return new RobotsPolicy(null, true);
35
+ }
36
+ static parse(text) {
37
+ return new RobotsPolicy(robotsParser(`${ORIGIN}/robots.txt`, text), false);
38
+ }
39
+ canFetch(userAgent, url) {
40
+ if (this.disallowAll)
41
+ return false;
42
+ if (!this.robot)
43
+ return true;
44
+ return this.robot.isAllowed(onOrigin(url), userAgent) ?? true;
45
+ }
46
+ }
47
+ /** The url's path and query on the stand-in origin; an unparseable url reads as `/`. */
48
+ function onOrigin(url) {
49
+ try {
50
+ const u = new URL(url);
51
+ return `${ORIGIN}${u.pathname}${u.search}`;
52
+ }
53
+ catch {
54
+ return `${ORIGIN}/`;
55
+ }
56
+ }
57
+ /** The robots policy for this url's scheme+host, fetched once each. */
58
+ export async function robotsFor(fetcher, url, cache) {
59
+ let key;
60
+ try {
61
+ const u = new URL(url);
62
+ key = `${u.protocol}//${u.host}`;
63
+ }
64
+ catch {
65
+ key = `https://${url}`;
66
+ }
67
+ const hit = cache.get(key);
68
+ if (hit)
69
+ return hit;
70
+ let policy;
71
+ try {
72
+ const resp = await fetcher.get(`${key}/robots.txt`);
73
+ policy = resp.status === 200 ? RobotsPolicy.parse(resp.text) : RobotsPolicy.allowAll();
74
+ }
75
+ catch (err) {
76
+ if (err instanceof FetchError && err.status !== null && err.status >= 500) {
77
+ policy = RobotsPolicy.disallowAll(); // "unavailable" is not "absent"
78
+ }
79
+ else if (err instanceof FetchError) {
80
+ policy = RobotsPolicy.allowAll(); // unreachable host: the web's allow default
81
+ }
82
+ else {
83
+ throw err;
84
+ }
85
+ }
86
+ cache.set(key, policy);
87
+ return policy;
88
+ }
89
+ export async function canFetch(fetcher, url, cache) {
90
+ return (await robotsFor(fetcher, url, cache)).canFetch(fetcher.userAgent, url);
91
+ }
92
+ //# sourceMappingURL=robots.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"robots.js","sourceRoot":"","sources":["../../src/fetch/robots.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * read: the web and files in, text out. Four doors: `./fetch` (polite HTTP, robots, the fetch
3
+ * ladder up to a browser), `./text` (HTML and PDF to text, a page to title, text and links),
4
+ * `./video` (a video or podcast from a URL or a file: captions, `watch`, audio to markdown, what a
5
+ * channel or search holds) and `./study` (claims that must quote a page, drafts that must cite a
6
+ * claim, the report). Every model call goes through `@wrenautomation/models`. No database here.
7
+ */
8
+ export * from "./fetch/index.js";
9
+ export * from "./study/index.js";
10
+ export * from "./text/index.js";
11
+ export * from "./video/index.js";
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * read: the web and files in, text out. Four doors: `./fetch` (polite HTTP, robots, the fetch
3
+ * ladder up to a browser), `./text` (HTML and PDF to text, a page to title, text and links),
4
+ * `./video` (a video or podcast from a URL or a file: captions, `watch`, audio to markdown, what a
5
+ * channel or search holds) and `./study` (claims that must quote a page, drafts that must cite a
6
+ * claim, the report). Every model call goes through `@wrenautomation/models`. No database here.
7
+ */
8
+ export * from "./fetch/index.js";
9
+ export * from "./study/index.js";
10
+ export * from "./text/index.js";
11
+ export * from "./video/index.js";
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * A claim as the synthesis reader sees it: one sentence that quotes one page. birdwatch's
3
+ * `claimExtractor` (`@wrenautomation/birdwatch/document`) draws claims out of a document; a
4
+ * study's drafts and report (`./drafts`, `./report`) cite them by number. The planner that
5
+ * lived here (queries per angle, which pages to read, the claim gate per angle) went in 0.5.0:
6
+ * birdwatch plans and runs.
7
+ */
8
+ export interface Claim {
9
+ /** The angle it answers: its index in the study's angles. */
10
+ angle: number;
11
+ claim: string;
12
+ quote: string;
13
+ source_url: string;
14
+ }
15
+ /**
16
+ * `{name}` marks filled in one pass: a "{angle}" or "$&" inside a question or
17
+ * a page stays text.
18
+ */
19
+ export declare const fill: (template: string, values: Readonly<Record<string, string>>) => string;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * A claim as the synthesis reader sees it: one sentence that quotes one page. birdwatch's
3
+ * `claimExtractor` (`@wrenautomation/birdwatch/document`) draws claims out of a document; a
4
+ * study's drafts and report (`./drafts`, `./report`) cite them by number. The planner that
5
+ * lived here (queries per angle, which pages to read, the claim gate per angle) went in 0.5.0:
6
+ * birdwatch plans and runs.
7
+ */
8
+ /**
9
+ * `{name}` marks filled in one pass: a "{angle}" or "$&" inside a question or
10
+ * a page stays text.
11
+ */
12
+ export const fill = (template, values) => template.replace(/\{(\w+)\}/g, (mark, name) => values[name] ?? mark);
13
+ //# sourceMappingURL=claims.js.map