@wrenautomation/birdwatch 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 (141) hide show
  1. package/LICENSE.md +61 -0
  2. package/README.md +320 -2
  3. package/dist/app/cli.d.ts +4 -0
  4. package/dist/app/cli.js +149 -0
  5. package/dist/app/cli.js.map +1 -0
  6. package/dist/app/engine-cli.d.ts +44 -0
  7. package/dist/app/engine-cli.js +596 -0
  8. package/dist/app/engine-cli.js.map +1 -0
  9. package/dist/app/index.d.ts +6 -0
  10. package/dist/app/index.js +7 -0
  11. package/dist/app/index.js.map +1 -0
  12. package/dist/app/serve.d.ts +14 -0
  13. package/dist/app/serve.js +102 -0
  14. package/dist/app/serve.js.map +1 -0
  15. package/dist/document/body.d.ts +6 -0
  16. package/dist/document/body.js +18 -0
  17. package/dist/document/body.js.map +1 -0
  18. package/dist/document/check.d.ts +35 -0
  19. package/dist/document/check.js +68 -0
  20. package/dist/document/check.js.map +1 -0
  21. package/dist/document/extract.d.ts +81 -0
  22. package/dist/document/extract.js +226 -0
  23. package/dist/document/extract.js.map +1 -0
  24. package/dist/document/index.d.ts +11 -0
  25. package/dist/document/index.js +12 -0
  26. package/dist/document/index.js.map +1 -0
  27. package/dist/document/model.d.ts +10 -0
  28. package/dist/document/model.js +2 -0
  29. package/dist/document/model.js.map +1 -0
  30. package/dist/document/readers.d.ts +24 -0
  31. package/dist/document/readers.js +87 -0
  32. package/dist/document/readers.js.map +1 -0
  33. package/dist/document/types.d.ts +99 -0
  34. package/dist/document/types.js +93 -0
  35. package/dist/document/types.js.map +1 -0
  36. package/dist/engine/agent.d.ts +475 -0
  37. package/dist/engine/agent.js +990 -0
  38. package/dist/engine/agent.js.map +1 -0
  39. package/dist/engine/canonical.d.ts +4 -0
  40. package/dist/engine/canonical.js +55 -0
  41. package/dist/engine/canonical.js.map +1 -0
  42. package/dist/engine/context.d.ts +9 -0
  43. package/dist/engine/context.js +141 -0
  44. package/dist/engine/context.js.map +1 -0
  45. package/dist/engine/cost.d.ts +13 -0
  46. package/dist/engine/cost.js +31 -0
  47. package/dist/engine/cost.js.map +1 -0
  48. package/dist/engine/index.d.ts +16 -0
  49. package/dist/engine/index.js +17 -0
  50. package/dist/engine/index.js.map +1 -0
  51. package/dist/engine/judge.d.ts +158 -0
  52. package/dist/engine/judge.js +323 -0
  53. package/dist/engine/judge.js.map +1 -0
  54. package/dist/engine/people.d.ts +34 -0
  55. package/dist/engine/people.js +125 -0
  56. package/dist/engine/people.js.map +1 -0
  57. package/dist/engine/plan.d.ts +6 -0
  58. package/dist/engine/plan.js +7 -0
  59. package/dist/engine/plan.js.map +1 -0
  60. package/dist/engine/plans/index.d.ts +19 -0
  61. package/dist/engine/plans/index.js +104 -0
  62. package/dist/engine/plans/index.js.map +1 -0
  63. package/dist/engine/ports.d.ts +76 -0
  64. package/dist/engine/ports.js +28 -0
  65. package/dist/engine/ports.js.map +1 -0
  66. package/dist/engine/recall.d.ts +42 -0
  67. package/dist/engine/recall.js +71 -0
  68. package/dist/engine/recall.js.map +1 -0
  69. package/dist/engine/state.d.ts +96 -0
  70. package/dist/engine/state.js +162 -0
  71. package/dist/engine/state.js.map +1 -0
  72. package/dist/engine/steps.d.ts +98 -0
  73. package/dist/engine/steps.js +435 -0
  74. package/dist/engine/steps.js.map +1 -0
  75. package/dist/engine/tree.d.ts +76 -0
  76. package/dist/engine/tree.js +148 -0
  77. package/dist/engine/tree.js.map +1 -0
  78. package/dist/engine/types.d.ts +250 -0
  79. package/dist/engine/types.js +168 -0
  80. package/dist/engine/types.js.map +1 -0
  81. package/dist/engine/view.d.ts +138 -0
  82. package/dist/engine/view.js +188 -0
  83. package/dist/engine/view.js.map +1 -0
  84. package/dist/index.d.ts +15 -0
  85. package/dist/index.js +16 -0
  86. package/dist/index.js.map +1 -0
  87. package/dist/ledger/files.d.ts +39 -0
  88. package/dist/ledger/files.js +113 -0
  89. package/dist/ledger/files.js.map +1 -0
  90. package/dist/ledger/ids.d.ts +4 -0
  91. package/dist/ledger/ids.js +9 -0
  92. package/dist/ledger/ids.js.map +1 -0
  93. package/dist/ledger/index.d.ts +11 -0
  94. package/dist/ledger/index.js +12 -0
  95. package/dist/ledger/index.js.map +1 -0
  96. package/dist/ledger/ledger.d.ts +46 -0
  97. package/dist/ledger/ledger.js +105 -0
  98. package/dist/ledger/ledger.js.map +1 -0
  99. package/dist/ledger/memory.d.ts +19 -0
  100. package/dist/ledger/memory.js +19 -0
  101. package/dist/ledger/memory.js.map +1 -0
  102. package/dist/ledger/records.d.ts +1424 -0
  103. package/dist/ledger/records.js +292 -0
  104. package/dist/ledger/records.js.map +1 -0
  105. package/dist/sources/arxiv.d.ts +105 -0
  106. package/dist/sources/arxiv.js +321 -0
  107. package/dist/sources/arxiv.js.map +1 -0
  108. package/dist/sources/exa.d.ts +19 -0
  109. package/dist/sources/exa.js +59 -0
  110. package/dist/sources/exa.js.map +1 -0
  111. package/dist/sources/fetcher.d.ts +35 -0
  112. package/dist/sources/fetcher.js +67 -0
  113. package/dist/sources/fetcher.js.map +1 -0
  114. package/dist/sources/google.d.ts +16 -0
  115. package/dist/sources/google.js +64 -0
  116. package/dist/sources/google.js.map +1 -0
  117. package/dist/sources/index.d.ts +17 -0
  118. package/dist/sources/index.js +18 -0
  119. package/dist/sources/index.js.map +1 -0
  120. package/dist/sources/junk.d.ts +10 -0
  121. package/dist/sources/junk.js +16 -0
  122. package/dist/sources/junk.js.map +1 -0
  123. package/dist/sources/ladder.d.ts +47 -0
  124. package/dist/sources/ladder.js +158 -0
  125. package/dist/sources/ladder.js.map +1 -0
  126. package/dist/sources/ledger.d.ts +7 -0
  127. package/dist/sources/ledger.js +35 -0
  128. package/dist/sources/ledger.js.map +1 -0
  129. package/dist/sources/paste.d.ts +8 -0
  130. package/dist/sources/paste.js +22 -0
  131. package/dist/sources/paste.js.map +1 -0
  132. package/dist/sources/searxng.d.ts +11 -0
  133. package/dist/sources/searxng.js +38 -0
  134. package/dist/sources/searxng.js.map +1 -0
  135. package/dist/sources/sites.d.ts +53 -0
  136. package/dist/sources/sites.js +285 -0
  137. package/dist/sources/sites.js.map +1 -0
  138. package/dist/sources/ytdlp.d.ts +43 -0
  139. package/dist/sources/ytdlp.js +117 -0
  140. package/dist/sources/ytdlp.js.map +1 -0
  141. package/package.json +61 -3
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The sources: one file each, registered by name. `searxng` and `exa` search the web, `arxiv`
3
+ * searches and reads papers, `youtube`
4
+ * searches and reads videos, `fetcher` and `ladder` open pages, `reddit` and `facebook` read a
5
+ * thread or a group post through the site's own route, `paste` is a person's list,
6
+ * `ledger` is what earlier runs saw.
7
+ */
8
+ export * from "./arxiv.js";
9
+ export * from "./exa.js";
10
+ export * from "./fetcher.js";
11
+ export * from "./google.js";
12
+ export * from "./ladder.js";
13
+ export * from "./ledger.js";
14
+ export * from "./paste.js";
15
+ export * from "./searxng.js";
16
+ export * from "./sites.js";
17
+ export * from "./ytdlp.js";
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The sources: one file each, registered by name. `searxng` and `exa` search the web, `arxiv`
3
+ * searches and reads papers, `youtube`
4
+ * searches and reads videos, `fetcher` and `ladder` open pages, `reddit` and `facebook` read a
5
+ * thread or a group post through the site's own route, `paste` is a person's list,
6
+ * `ledger` is what earlier runs saw.
7
+ */
8
+ export * from "./arxiv.js";
9
+ export * from "./exa.js";
10
+ export * from "./fetcher.js";
11
+ export * from "./google.js";
12
+ export * from "./ladder.js";
13
+ export * from "./ledger.js";
14
+ export * from "./paste.js";
15
+ export * from "./searxng.js";
16
+ export * from "./sites.js";
17
+ export * from "./ytdlp.js";
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sources/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,cAAc,YAAY,CAAC;AAC3B,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * A page that answered 200 and still holds nothing: a captcha wall, a 404 shell, an empty body.
3
+ * Both open sources ask before they return a document, so such a page is a miss with a reason
4
+ * (spend ok:false, a gap), never a read.
5
+ */
6
+ import type { Document } from "../document/index.js";
7
+ /** Fewer characters than this is no page: a shell, a redirect stub, a bare title. */
8
+ export declare const FLOOR = 200;
9
+ /** Why a document is junk, or null when it reads as a page. */
10
+ export declare function junkReason(doc: Document, status?: number | null): string | null;
@@ -0,0 +1,16 @@
1
+ /** Fewer characters than this is no page: a shell, a redirect stub, a bare title. */
2
+ export const FLOOR = 200;
3
+ const WALLS = /prove your humanity|just a moment|are you a (?:robot|human)|verify you are human|attention required|access denied|page not found|not found\b|403 forbidden|404\b|blocked|captcha|security check|enable javascript/i;
4
+ /** Why a document is junk, or null when it reads as a page. */
5
+ export function junkReason(doc, status) {
6
+ if (status !== undefined && status !== null && status >= 400)
7
+ return `HTTP ${status}`;
8
+ const title = doc.title?.trim() ?? "";
9
+ if (WALLS.test(title))
10
+ return `a wall, not a page: "${title}"`;
11
+ const n = doc.text.trim().length;
12
+ if (n < FLOOR)
13
+ return `only ${n} characters of text, no page`;
14
+ return null;
15
+ }
16
+ //# sourceMappingURL=junk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"junk.js","sourceRoot":"","sources":["../../src/sources/junk.ts"],"names":[],"mappings":"AAOA,qFAAqF;AACrF,MAAM,CAAC,MAAM,KAAK,GAAG,GAAG,CAAC;AAEzB,MAAM,KAAK,GACT,oNAAoN,CAAC;AAEvN,+DAA+D;AAC/D,MAAM,UAAU,UAAU,CAAC,GAAa,EAAE,MAAsB;IAC9D,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,IAAI,GAAG;QAAE,OAAO,QAAQ,MAAM,EAAE,CAAC;IACtF,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACtC,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,wBAAwB,KAAK,GAAG,CAAC;IAC/D,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC;IACjC,IAAI,CAAC,GAAG,KAAK;QAAE,OAAO,QAAQ,CAAC,8BAA8B,CAAC;IAC9D,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The fetch ladder as an open source: `web GET /fetch` on a site client, up to a browser. Only
3
+ * worth a call after the plain fetcher came back empty or blocked, so it sits after `fetcher` in
4
+ * a plan file's sources. Browser cost, and one of the worker's daily `fetch` bucket per call. A
5
+ * block the last rung still hit, a spent bucket or a page that is not HTML is a miss (a throw
6
+ * with the reason); a worker that did not answer is a Transient.
7
+ *
8
+ * Two wires to an autobrowse worker for a terminal run: `httpLadderClient` for its HTTP face
9
+ * (`GET $url/api/sites/web/fetch?url=&from=`, a bearer token); `ingressLadderClient` for the
10
+ * Restate ingress the desk worker sits behind (`POST $url/desk/call`, the same bearer).
11
+ */
12
+ import { type LadderClient, type LadderTier } from "@wrenautomation/read/fetch";
13
+ import { type Media, type Reader } from "../document/index.js";
14
+ import type { Source } from "../engine/ports.js";
15
+ export declare function ladderSource(o: {
16
+ sites: LadderClient;
17
+ from?: LadderTier;
18
+ readers?: Map<Media, Reader>;
19
+ }): Source;
20
+ export declare class LadderCallError extends Error {
21
+ readonly status: number;
22
+ /** The worker's access code (`need_login`, `platform_blocked`, `rate_limited`, `upstream_broken`), when it sent one. */
23
+ readonly code: string | null;
24
+ constructor(message: string, status: number,
25
+ /** The worker's access code (`need_login`, `platform_blocked`, `rate_limited`, `upstream_broken`), when it sent one. */
26
+ code?: string | null);
27
+ }
28
+ /** The HTTP wire to an autobrowse worker, enough for the ladder. wren has the whole client. */
29
+ export declare function httpLadderClient(o: {
30
+ url: string;
31
+ token?: string | null;
32
+ fetchFn?: typeof fetch;
33
+ }): LadderClient;
34
+ /**
35
+ * The Restate ingress wire to an autobrowse worker: `POST $url/$service/call` with the site
36
+ * call as its body, the way wren's CLI reaches the desk. `service` is `desk` (the Mac) or
37
+ * `sites` (a box); `caller` names who asks, for the worker's per-caller usage. Restate answers a
38
+ * refused call as `{"code":429,"message":"…"}` under that status: a 429 is the ladder's "cap".
39
+ * A coded refusal adds `"metadata":{"code":"need_login"}`.
40
+ */
41
+ export declare function ingressLadderClient(o: {
42
+ url: string;
43
+ token?: string | null;
44
+ service?: string;
45
+ caller?: string;
46
+ fetchFn?: typeof fetch;
47
+ }): LadderClient;
@@ -0,0 +1,158 @@
1
+ /**
2
+ * The fetch ladder as an open source: `web GET /fetch` on a site client, up to a browser. Only
3
+ * worth a call after the plain fetcher came back empty or blocked, so it sits after `fetcher` in
4
+ * a plan file's sources. Browser cost, and one of the worker's daily `fetch` bucket per call. A
5
+ * block the last rung still hit, a spent bucket or a page that is not HTML is a miss (a throw
6
+ * with the reason); a worker that did not answer is a Transient.
7
+ *
8
+ * Two wires to an autobrowse worker for a terminal run: `httpLadderClient` for its HTTP face
9
+ * (`GET $url/api/sites/web/fetch?url=&from=`, a bearer token); `ingressLadderClient` for the
10
+ * Restate ingress the desk worker sits behind (`POST $url/desk/call`, the same bearer).
11
+ */
12
+ import { Transient } from "@wrenautomation/models";
13
+ import { fetchLadder } from "@wrenautomation/read/fetch";
14
+ import { defaultReaders } from "../document/index.js";
15
+ import { isPage } from "./fetcher.js";
16
+ import { junkReason } from "./junk.js";
17
+ export function ladderSource(o) {
18
+ const readers = o.readers ?? defaultReaders();
19
+ return {
20
+ name: "ladder",
21
+ kind: "open",
22
+ cost: "browser",
23
+ opens: isPage,
24
+ async open(h) {
25
+ if (!isPage(h.url))
26
+ throw new Error(`ladder: not a page: ${h.url}`);
27
+ const r = await fetchLadder(o.sites, h.url, o.from ?? "impersonate");
28
+ // offline: the worker did not answer, worth another try. The rest is a miss, like the
29
+ // fetcher's 403: the read step writes a gap and goes on to the next hit.
30
+ if ("failed" in r) {
31
+ if (r.failed === "offline")
32
+ throw new Transient("ladder: offline");
33
+ throw new Error("ladder: the worker's fetch bucket is spent for today");
34
+ }
35
+ if (r.fetched.block)
36
+ throw new Error(`ladder: ${r.fetched.block} at ${r.fetched.tier}`);
37
+ // The ladder answers the body as text: only HTML reads right that way.
38
+ const type = r.fetched.type?.split(";")[0]?.trim().toLowerCase();
39
+ if (type && type !== "text/html" && type !== "application/xhtml+xml")
40
+ throw new Error(`ladder: ${type} is not html`);
41
+ const html = readers.get("html");
42
+ if (!html)
43
+ throw new Error("ladder: no html reader");
44
+ const doc = await html.read({
45
+ url: r.fetched.finalUrl || h.url,
46
+ text: r.fetched.html,
47
+ ...(h.title ? { title: h.title } : {}),
48
+ });
49
+ if (!doc)
50
+ throw new Error("ladder: nothing to read in the page");
51
+ const junk = junkReason(doc, r.fetched.status);
52
+ if (junk)
53
+ throw new Error(`ladder: ${junk} at ${r.fetched.tier}`);
54
+ return doc;
55
+ },
56
+ };
57
+ }
58
+ export class LadderCallError extends Error {
59
+ status;
60
+ code;
61
+ constructor(message, status,
62
+ /** The worker's access code (`need_login`, `platform_blocked`, `rate_limited`, `upstream_broken`), when it sent one. */
63
+ code = null) {
64
+ super(message);
65
+ this.status = status;
66
+ this.code = code;
67
+ this.name = "LadderCallError";
68
+ }
69
+ }
70
+ /** The HTTP wire to an autobrowse worker, enough for the ladder. wren has the whole client. */
71
+ export function httpLadderClient(o) {
72
+ const fetchFn = o.fetchFn ?? fetch;
73
+ const base = o.url.replace(/\/$/, "");
74
+ return {
75
+ async call(site, method, path, input, as) {
76
+ const read = method === "GET" || method === "DELETE";
77
+ let url = `${base}/api/sites/${site}${path}`;
78
+ const q = new URLSearchParams();
79
+ if (read && input && typeof input === "object")
80
+ for (const [k, v] of Object.entries(input))
81
+ if (v !== undefined)
82
+ q.set(k, String(v));
83
+ // autobrowse takes `account` beside the query, for writes too.
84
+ if (as?.account)
85
+ q.set("account", as.account);
86
+ if (q.size)
87
+ url += `?${q}`;
88
+ const res = await fetchFn(url, {
89
+ method,
90
+ headers: {
91
+ ...(read ? {} : { "content-type": "application/json" }),
92
+ ...(o.token ? { authorization: `Bearer ${o.token}` } : {}),
93
+ },
94
+ ...(read ? {} : { body: JSON.stringify(input ?? {}) }),
95
+ signal: AbortSignal.timeout(180_000),
96
+ });
97
+ const text = await res.text();
98
+ const body = text ? JSON.parse(text) : null;
99
+ if (!res.ok) {
100
+ const b = body;
101
+ throw new LadderCallError(`${site} ${method} ${path}: ${res.status} ${b?.error ?? res.statusText}`, res.status, b?.code ?? null);
102
+ }
103
+ return body;
104
+ },
105
+ };
106
+ }
107
+ /**
108
+ * The Restate ingress wire to an autobrowse worker: `POST $url/$service/call` with the site
109
+ * call as its body, the way wren's CLI reaches the desk. `service` is `desk` (the Mac) or
110
+ * `sites` (a box); `caller` names who asks, for the worker's per-caller usage. Restate answers a
111
+ * refused call as `{"code":429,"message":"…"}` under that status: a 429 is the ladder's "cap".
112
+ * A coded refusal adds `"metadata":{"code":"need_login"}`.
113
+ */
114
+ export function ingressLadderClient(o) {
115
+ const fetchFn = o.fetchFn ?? fetch;
116
+ const base = o.url.replace(/\/$/, "");
117
+ const service = o.service ?? "desk";
118
+ const caller = o.caller ?? "birdwatch";
119
+ return {
120
+ async call(site, method, path, input, as) {
121
+ const res = await fetchFn(`${base}/${service}/call`, {
122
+ method: "POST",
123
+ headers: {
124
+ "content-type": "application/json",
125
+ "idempotency-key": crypto.randomUUID(),
126
+ ...(o.token ? { authorization: `Bearer ${o.token}` } : {}),
127
+ },
128
+ body: JSON.stringify({
129
+ site,
130
+ method,
131
+ path,
132
+ input: input ?? {},
133
+ caller,
134
+ ...(as?.account ? { account: as.account } : {}),
135
+ }),
136
+ signal: AbortSignal.timeout(180_000),
137
+ });
138
+ const text = await res.text();
139
+ let body = null;
140
+ try {
141
+ body = text ? JSON.parse(text) : null;
142
+ }
143
+ catch {
144
+ body = text;
145
+ }
146
+ if (!res.ok) {
147
+ const said = body && typeof body === "object" && "message" in body
148
+ ? String(body.message)
149
+ : text || res.statusText;
150
+ // A coded refusal: autobrowse puts the access code in the terminal error's metadata.
151
+ const code = body?.metadata?.code;
152
+ throw new LadderCallError(`${site} ${method} ${path}: ${res.status} ${said}`, res.status, typeof code === "string" ? code : null);
153
+ }
154
+ return body;
155
+ },
156
+ };
157
+ }
158
+ //# sourceMappingURL=ladder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ladder.js","sourceRoot":"","sources":["../../src/sources/ladder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The ledger as a source: hits earlier runs saw whose title or snippet holds every word of the
3
+ * query. A run that starts where the last one got to. Free.
4
+ */
5
+ import type { Source } from "../engine/ports.js";
6
+ import type { Ledger } from "../ledger/ledger.js";
7
+ export declare function ledgerSource(ledger: Ledger, n?: number): Source;
@@ -0,0 +1,35 @@
1
+ import { ofType } from "../ledger/ledger.js";
2
+ const wordsOf = (s) => s
3
+ .toLowerCase()
4
+ .split(/[^\p{L}\p{N}]+/u)
5
+ .filter((w) => w.length > 2);
6
+ export function ledgerSource(ledger, n = 10) {
7
+ return {
8
+ name: "ledger",
9
+ kind: "search",
10
+ cost: "free",
11
+ async search(q) {
12
+ const want = wordsOf(q.terms);
13
+ if (!want.length)
14
+ return [];
15
+ const hits = ofType(await ledger.scan({ type: "hit" }), "hit").filter((h) => h.run !== q.run);
16
+ const out = new Map();
17
+ for (const h of hits) {
18
+ const have = new Set(wordsOf(`${h.title ?? ""} ${h.snippet ?? ""}`));
19
+ if (want.every((w) => have.has(w)) && !out.has(h.key))
20
+ out.set(h.key, h);
21
+ }
22
+ return [...out.values()]
23
+ .slice(0, n)
24
+ .map(({ url, title, channel, length, date, snippet }) => ({
25
+ url,
26
+ ...(title ? { title } : {}),
27
+ ...(channel ? { channel } : {}),
28
+ ...(length !== undefined ? { length } : {}),
29
+ ...(date ? { date } : {}),
30
+ ...(snippet ? { snippet } : {}),
31
+ }));
32
+ },
33
+ };
34
+ }
35
+ //# sourceMappingURL=ledger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ledger.js","sourceRoot":"","sources":["../../src/sources/ledger.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAE7C,MAAM,OAAO,GAAG,CAAC,CAAS,EAAE,EAAE,CAC5B,CAAC;KACE,WAAW,EAAE;KACb,KAAK,CAAC,iBAAiB,CAAC;KACxB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AAEjC,MAAM,UAAU,YAAY,CAAC,MAAc,EAAE,CAAC,GAAG,EAAE;IACjD,OAAO;QACL,IAAI,EAAE,QAAQ;QACd,IAAI,EAAE,QAAQ;QACd,IAAI,EAAE,MAAM;QACZ,KAAK,CAAC,MAAM,CAAC,CAAC;YACZ,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;YAC9B,IAAI,CAAC,IAAI,CAAC,MAAM;gBAAE,OAAO,EAAE,CAAC;YAC5B,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC;YAC9F,MAAM,GAAG,GAAG,IAAI,GAAG,EAAiC,CAAC;YACrD,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;gBACrB,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,EAAE,IAAI,CAAC,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;gBACrE,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;oBAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;YAC3E,CAAC;YACD,OAAO,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC;iBACrB,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;iBACX,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;gBACxD,GAAG;gBACH,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC3B,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC/B,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC3C,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACzB,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAChC,CAAC,CAAC,CAAC;QACR,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * A person's URLs as a source: the hits they pasted come back on the first search of a run,
3
+ * marked `from: human` and `by` them. Every query after that gets nothing: the list was given
4
+ * once.
5
+ */
6
+ import type { Source } from "../engine/ports.js";
7
+ import type { HitViewIn } from "../engine/types.js";
8
+ export declare function paste(hits: readonly HitViewIn[], by: string): Source;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * A person's URLs as a source: the hits they pasted come back on the first search of a run,
3
+ * marked `from: human` and `by` them. Every query after that gets nothing: the list was given
4
+ * once.
5
+ */
6
+ export function paste(hits, by) {
7
+ let given = false;
8
+ return {
9
+ name: "paste",
10
+ kind: "search",
11
+ cost: "free",
12
+ from: "human",
13
+ by,
14
+ async search() {
15
+ if (given)
16
+ return [];
17
+ given = true;
18
+ return hits.map((h) => ({ ...h }));
19
+ },
20
+ };
21
+ }
22
+ //# sourceMappingURL=paste.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paste.js","sourceRoot":"","sources":["../../src/sources/paste.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,MAAM,UAAU,KAAK,CAAC,IAA0B,EAAE,EAAU;IAC1D,IAAI,KAAK,GAAG,KAAK,CAAC;IAClB,OAAO;QACL,IAAI,EAAE,OAAO;QACb,IAAI,EAAE,QAAQ;QACd,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,OAAO;QACb,EAAE;QACF,KAAK,CAAC,MAAM;YACV,IAAI,KAAK;gBAAE,OAAO,EAAE,CAAC;YACrB,KAAK,GAAG,IAAI,CAAC;YACb,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;QACrC,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * SearXNG: a self-hosted metasearch, `GET $url/search?q=&format=json`. Free. `token` goes as a
3
+ * bearer for an instance behind an authenticating proxy (Wren's, on the VPS).
4
+ */
5
+ import type { Source } from "../engine/ports.js";
6
+ export declare function searxng(o: {
7
+ url: string;
8
+ token?: string;
9
+ fetchFn?: typeof fetch;
10
+ n?: number;
11
+ }): Source;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * SearXNG: a self-hosted metasearch, `GET $url/search?q=&format=json`. Free. `token` goes as a
3
+ * bearer for an instance behind an authenticating proxy (Wren's, on the VPS).
4
+ */
5
+ import { httpError, transport } from "@wrenautomation/models";
6
+ export function searxng(o) {
7
+ const fetchFn = o.fetchFn ?? fetch;
8
+ const base = o.url.replace(/\/$/, "");
9
+ return {
10
+ name: "searxng",
11
+ kind: "search",
12
+ cost: "free",
13
+ async search(q) {
14
+ const u = new URL(`${base}/search`);
15
+ u.searchParams.set("q", q.site ? `site:${q.site} ${q.terms}` : q.terms);
16
+ u.searchParams.set("format", "json");
17
+ const res = await fetchFn(u, {
18
+ signal: AbortSignal.timeout(30_000),
19
+ ...(o.token ? { headers: { authorization: `Bearer ${o.token}` } } : {}),
20
+ }).catch((err) => {
21
+ throw transport("searxng", err);
22
+ });
23
+ if (!res.ok)
24
+ throw httpError("searxng", res);
25
+ const body = (await res.json());
26
+ return (body.results ?? [])
27
+ .filter((r) => typeof r.url === "string")
28
+ .slice(0, o.n ?? 10)
29
+ .map((r) => ({
30
+ url: r.url,
31
+ ...(r.title ? { title: r.title } : {}),
32
+ ...(r.content ? { snippet: r.content } : {}),
33
+ ...(r.publishedDate ? { date: r.publishedDate.slice(0, 10) } : {}),
34
+ }));
35
+ },
36
+ };
37
+ }
38
+ //# sourceMappingURL=searxng.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"searxng.js","sourceRoot":"","sources":["../../src/sources/searxng.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAU9D,MAAM,UAAU,OAAO,CAAC,CAKvB;IACC,MAAM,OAAO,GAAG,CAAC,CAAC,OAAO,IAAI,KAAK,CAAC;IACnC,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACtC,OAAO;QACL,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,QAAQ;QACd,IAAI,EAAE,MAAM;QACZ,KAAK,CAAC,MAAM,CAAC,CAAC;YACZ,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,IAAI,SAAS,CAAC,CAAC;YACpC,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;YACxE,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YACrC,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,CAAC,EAAE;gBAC3B,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC;gBACnC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,CAAC,CAAC,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACxE,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;gBACf,MAAM,SAAS,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;YAClC,CAAC,CAAC,CAAC;YACH,IAAI,CAAC,GAAG,CAAC,EAAE;gBAAE,MAAM,SAAS,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;YAC7C,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAgC,CAAC;YAC/D,OAAO,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC;iBACxB,MAAM,CAAC,CAAC,CAAC,EAAsC,EAAE,CAAC,OAAO,CAAC,CAAC,GAAG,KAAK,QAAQ,CAAC;iBAC5E,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;iBACnB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACX,GAAG,EAAE,CAAC,CAAC,GAAG;gBACV,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACtC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC5C,GAAG,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACnE,CAAC,CAAC,CAAC;QACR,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Readers for one site each, over that site's own read route on an autobrowse worker (the same
3
+ * wire as the ladder). Each claims only its site's URLs and goes before the general openers
4
+ * (`first`): the route answers the thread or the post as data, where a fetched page is a wall or a
5
+ * JavaScript shell.
6
+ *
7
+ * `reddit`: a thread (`/comments/{id}`) through `reddit-public` (signed out, the RSS feed), then,
8
+ * when the caller named an account (`BIRDWATCH_ACCOUNTS=reddit=reddit@alt`), through `reddit`
9
+ * signed in as it. Signed out goes first: read volume on a signed-in account is what Reddit
10
+ * flags. A follow from a user's page (`/user/{name}`) lists their top posts and comments in one
11
+ * read (`/user/{name}/overview`), signed out. `facebook`: one public group post through `fb-public`, always signed out (Wren's Meta
12
+ * login runs the ads). Design: autobrowse `designs/2026-10-28-signed-in-reads.md`.
13
+ *
14
+ * A route that answered no (a 404, a spent cap, a refusal) is a miss with its reason and the
15
+ * worker's access code; a worker that did not answer, or a busy profile (503), is a Transient.
16
+ * A signed-out miss reads signed in, except a spent cap or a broken site (`rate_limited`,
17
+ * `upstream_broken`): signing in fixes neither.
18
+ */
19
+ import type { LadderClient } from "@wrenautomation/read/fetch";
20
+ import type { Document } from "../document/index.js";
21
+ import type { Source } from "../engine/ports.js";
22
+ import type { HitViewIn } from "../engine/types.js";
23
+ /** `reddit=reddit@alt,linkedin=linkedin@wren`: the account each site reader signs in as. */
24
+ export declare function accountsFrom(spec: string | undefined): Map<string, string>;
25
+ /** A Reddit thread's id, and the comment's when the URL is one comment's permalink. */
26
+ export declare function redditThread(url: string): {
27
+ article: string;
28
+ comment?: string;
29
+ } | null;
30
+ /** A Reddit user's name from their page (`/user/{name}`, `/u/{name}`). */
31
+ export declare function redditUser(url: string): string | null;
32
+ /** A user's overview (posts and comments mixed) as hits: each its permalink, title and words. */
33
+ export declare function userHits(body: unknown): HitViewIn[];
34
+ /** A Facebook group post's group and post ids. */
35
+ export declare function facebookPost(url: string): {
36
+ group: string;
37
+ post: string;
38
+ } | null;
39
+ type Obj = Record<string, unknown>;
40
+ /** A thread (`[post listing, comments listing]`) as text: the post, then each comment. */
41
+ export declare function threadDocument(url: string, body: unknown): Document;
42
+ /** fb-public's `{ post, comments }` as text: the post's whole text, then each comment. */
43
+ export declare function groupPostDocument(url: string, body: unknown): Document;
44
+ /** One route call; a refusal is a miss with the worker's words, no answer a Transient. */
45
+ export declare function routeCall(sites: LadderClient, who: string, site: string, path: string, input: Obj, account?: string): Promise<unknown>;
46
+ export declare function redditSource(o: {
47
+ sites: LadderClient;
48
+ account?: string;
49
+ }): Source;
50
+ export declare function facebookSource(o: {
51
+ sites: LadderClient;
52
+ }): Source;
53
+ export {};