@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
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,321 @@
1
- # Temporary Holding Version
1
+ # birdwatch
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
+ Research from the open web, driven by an agent one step per command. The agent plans, triages,
4
+ reviews and stops; birdwatch searches, opens pages, extracts findings behind a quote gate, checks
5
+ them, and keeps every record in a ledger. Every finding is a sentence with a quote on a page the
6
+ run read. `skills/birdwatch/SKILL.md` (in this repo) is how an agent drives it.
7
+
8
+ Reading the web and files (the fetch ladder, HTML and PDF to text, video) is
9
+ [`@wrenautomation/read`](https://github.com/wrenautomation/engine/tree/main/packages/read)'s. Every model call goes through
10
+ [`@wrenautomation/models`](https://github.com/wrenautomation/engine/tree/main/packages/models). Until 0.29.0 this was three packages: raven (a model planner, gone at 0.30.0: the
11
+ agent plans), falcon (`./document`) and weaver (the engine, sources, ledger and CLI).
12
+
13
+ | Import | Does |
14
+ |---|---|
15
+ | `@wrenautomation/birdwatch/engine` | `Source` (a name, `search` and/or `open`, a cost), `registry` and the plan's allow-list; the agent's calls (`planRound`, `search`, `open`, `extract`, `check`, `add`, `accept`, `brief`), one step each; the judge's passes (`judgeHits`, `judgeDocuments`, `judgeFindings`, `judgeStats`); `fold` rebuilds a run's state from its records, so a step run again skips what is done; `Transient`, the failure worth another try; the ask and plan file shapes (`Ask`, `PlanFile`, `Query`, `QueryTree`, `Edge`, `Snapshot`) and a stock plan file per ask kind (`web`, `video`, `channel`, `person`, `site`: sources, budget, `age`, `rules`, the query tree's guide) |
16
+ | `@wrenautomation/birdwatch/sources` | `searxng`, `exa` (search), `youtube` (search and read, yt-dlp), `fetcher` (open, the polite fetcher), `ladder` (open, autobrowse's fetch ladder up to a browser), `reddit` and `facebook` (open a thread or a group post through autobrowse's site routes), `google` (Google's own page through the desk's browser: each result a hit, the AI Overview one hit with its text as the snippet, each page it cites a hit), `arxiv` (search and read: the API inside `BIRDWATCH_ARXIV_CATEGORIES`, each hit led by its OpenAlex citation count and comment; the paper read whole from its HTML, else its PDF; one call per 3 s), `paste` (a person's URLs), `ledger` (hits earlier runs saw). A query with a `site` skips a searcher that cannot keep to it (`Source.keeps`: `youtube` keeps to YouTube only). A follow (`Source.follows`, `follow`) lists a page's author's posts (`youtube`: a channel's uploads, from a video or the channel, and a TikTok profile; `reddit`: a user's top posts and comments) or pages like it (`exa`: findSimilar) |
17
+ | `@wrenautomation/birdwatch/ledger` | the records (`query`, `hit`, `document`, `finding`, `verdict`, `call`, `spend`, `snapshot`, `edge`, `done`, `steer`, `gap`, `check`, `pattern`, `found`, `judge`, `person`), `FilesLedger` (a folder: `runs/<run>/<writer>.jsonl`, `plans/<kind>.json`), `MemoryLedger`, yield rows per source, person, channel and kind |
18
+ | `@wrenautomation/birdwatch/document` | the document side: `Document`, `Finding`, `Proof`, `Verdict` (zod), readers by media (`html`, `pdf`, `text`), `claimExtractor` (findings that quote the document, through the gate, and a verdict on the document: its quality and the ask's gap; no cap on findings or their length, and an answer the model's output cap cut is continued from its last kept quote), the `Model` seam it takes and the `Cut` a model throws when its cap cut an answer |
19
+
20
+ Every record carries `from` (ai, human or tool) and `by` (an agent or model id, a source, a
21
+ person). A person's keep or drop is the same record as the agent's. The plan file in
22
+ `plans/<kind>.json` beside the ledger (sources, budget, age, rules, the tree guide) is where a run
23
+ learns: `birdwatch learn <kind>` writes its next version from what a run taught, on the person's
24
+ agreement; a kind with no tuned file runs the stock one (`birdwatch plans <kind>` prints it).
25
+ `brief.guide` shows the kind's age sentence and rules to the agent.
26
+
27
+ ## CLI
28
+
29
+ Needs Node 24+. `read` and `watch` need yt-dlp and ffmpeg.
30
+
31
+ ```sh
32
+ npm i @wrenautomation/birdwatch
33
+ npx birdwatch fetch https://example.com/report --contact you@example.com # a page as text
34
+ GEMINI_API_KEY=... npx birdwatch read https://www.youtube.com/watch?v=... # captions + screen
35
+ COHERE_API_KEY=... npx birdwatch read https://www.instagram.com/reel/... # watched: speech, screen, cuts
36
+ npx birdwatch list 'ytsearch10:cold email' --to 10 # hits as JSON, no downloads
37
+ npx birdwatch text report.pdf # a file on disk as text
38
+ npx birdwatch page saved.html --url https://example.com/report # title, text, links as JSON
39
+ COHERE_API_KEY=... npx birdwatch watch talk.mp4 # speech, on-screen text, cuts
40
+ npx birdwatch plans web > ledger/plans/web.json # a stock plan file, to tune
41
+ ```
42
+
43
+ An agent (Claude Code, or any agent with a shell) drives the engine one command at a time: it
44
+ plans, triages, reviews and stops in its own head, and the engine does the fixed work (search,
45
+ open, extract behind the quote gate, check) and keeps the records. Every command prints JSON.
46
+
47
+ ```sh
48
+ export BIRDWATCH_AGENT=claude-code # the agent's id on every record it writes
49
+ birdwatch plan "how do staffing firms win repeat clients" --known "referrals convert" # a run; prints its id
50
+ birdwatch plan "who buys growth help" --premise "coaches are the buyer" # a premise gets a tree facet that tests it
51
+ birdwatch brief 01M5YS... # the ask, budget, sources, what is open, hits, findings, cost
52
+ birdwatch recall staffing repeat clients --not 01M5YS... # earlier runs' findings: quote, URL, source date, run; --also <ledger> per extra folder
53
+ birdwatch search 01M5YS... "staffing repeat client rate" --why "the ask's words" # one query, then every searcher
54
+ echo '[{"terms":"...","why":"..."},{"terms":"...","why":"..."}]' | birdwatch search 01M5YS... # a batch
55
+ birdwatch search 01M5YS... < tree.json # a query tree, one tier: {facets: [{name, why, tries: [[{terms, why}], [...]]}]}
56
+ birdwatch search 01M5YS... < tree.json # after triage, the same file: the next try of each facet still thin in kept hits
57
+ birdwatch add call 01M5YS... 01M5YT... keep --why "a survey with numbers" # triage: a hit kept
58
+ birdwatch add call 01M5YS... 01M5YU... drop --why "a vendor page"
59
+ birdwatch open 01M5YS... 01M5YT... --url https://a.example/report --why "numbers" # keep these, read every kept hit
60
+ birdwatch open 01M5YS... --with ladder # an opener the run lacks joins it, to open only
61
+ birdwatch show 01M5YS... 01M5YV... # a document's text, for the agent to read itself
62
+ birdwatch add finding 01M5YS... 01M5YV... --text "412 firms were surveyed." --quote "A 2025 survey of 412 firms"
63
+ birdwatch add check 01M5YS... 01M5YW... --ok --why "the quote says it" # the agent's own check; --fail also drops
64
+ birdwatch extract 01M5YS... # or a model behind the quote gate, every document without a verdict
65
+ birdwatch check 01M5YS... -n 3 # or a model's sample of findings read against their documents
66
+ birdwatch check 01M5YS... --patterns # every unchecked finding a pattern rests on
67
+ birdwatch accept 01M5YS... keep --over 0.8 --why "serves the ask" # the judge's hints in bulk, at the agent's bar
68
+ birdwatch accept 01M5YS... class --min 0.8 --why "head of page" # also: drop --under 0.2
69
+ birdwatch add gap 01M5YS... "no UK numbers yet"
70
+ birdwatch add steer 01M5YS... "numbers only" --human will # the person's word, relayed
71
+ birdwatch add pattern 01M5YS... "Repeat clients come from referrals." --on 01M5YV... 01M5YW... # what findings say together; sites counted
72
+ birdwatch add edge 01M5YS... again --why "thin on the UK" # the review: a new round
73
+ birdwatch plan --run 01M5YS... # round 2: known, steer and what round 1 left open, in its snapshot
74
+ birdwatch plan "coaches: who pays for setters" --parent 01M5YS... --why "the best niche of round 1" # a child run
75
+ birdwatch add dead 01M5YT... spend --why "found consumers, not buyers" # later tries skip the facet
76
+ birdwatch follow 01M5YS... --from 01M5YV... --as posts --why "the speaker's other talks" # the author's own posts; --as similar: pages like it; a paper: --as refs (what it cites) or citers (what cites it)
77
+ birdwatch add person 01M5YS... "Bee Speaker" --profile https://www.youtube.com/@bee --aka Bee --why "one speaker" # one person across sites
78
+ birdwatch learn web --sources searxng exa --rounds 4 # the plan file's next version, after a good run
79
+ birdwatch learn web --thin 2 # the tree's guide: a try under 2 new hits falls through
80
+ birdwatch learn web --show # the current one
81
+ birdwatch add edge 01M5YS... stop --why "enough"
82
+ ```
83
+
84
+ `plan` opens a round: its snapshot carries the ask, what is known (the findings so far), every
85
+ steer, what the last round left open, the allow-list and the budget. No queries: the agent's come
86
+ through `search` or `add query`, never capped. The budget is a guard rail: `brief` shows it, and
87
+ an edge `again` past `budget.rounds` prints a warning, never a refusal. `search`, `open`,
88
+ `extract` and `check` each run one step and print what it wrote (a document's text as `chars`);
89
+ a step that gave up exits 2 with its note. `open` reads every kept hit of the run that has no
90
+ document; `extract` every document without a verdict; `check` a sample across documents of the
91
+ kept findings not yet checked. `add` writes one record by hand: a query, a finding (its quote
92
+ must be on the document word for word, or the add fails with the gate's why), a check (the
93
+ agent's own read of a finding against its document; a fail also drops it), a call (keep or
94
+ drop a hit or a finding), an edge (`again`, `stop`, `back --to <step>`), a gap, or a steer (always
95
+ the person's: `--human [name]`). Every `add` takes `--why` where a decision needs one. A
96
+ `--human` record is the person's word through the agent: `from: human`, `by` their name.
97
+
98
+ A round's search is a tree when the agent gives one: facets (the sides a researcher covers: a
99
+ number, a how, a who, a counter-view, a primary source; the plan file's `tree` guide names them),
100
+ each with tries in order, each try a list of queries. `search` runs one tier per call, in one
101
+ fetch: the first call, every facet's first try; each call after, the next try of each facet
102
+ with fewer kept hits than the guide's `thin` (3 by default). A facet at `thin` is done; a facet
103
+ with hits no call has named waits for triage; a facet out of tries and still thin is a gap,
104
+ written once. The yield a try falls through on is what triage kept, never what a searcher
105
+ returned: every searcher returns a page. `--all` runs every facet's next try, whatever triage
106
+ kept. A page a second query brings back is a `found` record on the first hit, never a second
107
+ hit, and it counts as kept for every facet that reached it. `brief.hits.uncalled` lists each
108
+ hit with the facets that found it, most first; `brief.tree` shows every try with what it wrote,
109
+ `kept` and `uncalled` per facet. A guide facet no query served is named in the note.
110
+ `learn --thin <n>` or `--tree <file>` tunes the guide.
111
+
112
+ The agent reads for itself: `show <run> <document>` prints a document's stored text, `add
113
+ finding` writes what it read behind the same quote gate, `add check` its own check. `extract` and
114
+ `check` put a model behind the same gate instead, for bulk; both paths write the same records.
115
+
116
+ The person, through the agent or by hand:
117
+
118
+ ```sh
119
+ npx birdwatch add steer 01M5YS... "numbers only; skip vendor pages" --human # what you say: the brief shows it first
120
+ npx birdwatch add gap 01M5YS... "no UK numbers yet" --human # what the run lacks: the next plan reads it
121
+ npx birdwatch add edge 01M5YS... back --to fetch --why "searxng was down" --human # the step to run again
122
+ npx birdwatch trace 01M5YS... --type finding spend # the run's records, JSON lines
123
+ npx birdwatch add call 01M5YS... 01M5YT... drop --why "a forecast, not a result" --human # a call in your name
124
+ npx birdwatch yield --source exa # hits, reads, proposed, kept
125
+ npx birdwatch sources # what this machine can run
126
+ npx birdwatch serve --port 4747 # the ledger over HTTP for a viewer, read only
127
+ ```
128
+
129
+ The model is `anthropic` by default (Claude Haiku direct, `ANTHROPIC_API_KEY`; `--model
130
+ anthropic:sonnet|opus|id` for another), `--model gateway[:alias]` for Wren's LLM gateway
131
+ (`WREN_LLM_GATEWAY_URL`, `WREN_LLM_GATEWAY_TOKEN`), or any command that reads a prompt on stdin
132
+ and prints the answer (`claude -p --output-format json` is Claude Code headless). The model does
133
+ the bulk (extract, check) only; the agent decides. Sources come
134
+ from the environment: `SEARXNG_URL` (and `SEARXNG_TOKEN`, a bearer, for an instance behind a
135
+ proxy), `EXA_API_KEY` (or `_1..n`), `GEMINI_API_KEY` for videos without captions,
136
+ `BIRDWATCH_CONTACT` for the fetcher and `arxiv` (no contact, no page opens; `OPENALEX_API_KEY`, free, gives `arxiv` its own citation budget, and `SEMANTIC_SCHOLAR_API_KEY`, free, its own rate for `--as refs`; `--sources arxiv` names it, since no stock plan file does), and for the ladder either
137
+ `AUTOBROWSE_URL` and `AUTOBROWSE_TOKEN` (a worker's HTTP face) or `RESTATE_INGRESS_URL` and
138
+ `RESTATE_AUTH_TOKEN` (the Restate ingress a desk worker sits behind: `POST /desk/call`;
139
+ `AUTOBROWSE_SERVICE` for another service name). The same worker gives the `reddit` and
140
+ `facebook` readers. `BIRDWATCH_ACCOUNTS` names the account each site reader may sign in as
141
+ (`reddit=reddit@alt`); a site with no entry reads signed out only. The ledger folder is `--ledger` or
142
+ `BIRDWATCH_LEDGER` (default `./ledger`); `--writer` or `BIRDWATCH_WRITER` names your file in it.
143
+ The judge is `--judge` or `BIRDWATCH_JUDGE` (`jev`, `jev:<model>`, `fake`, `off`); with neither,
144
+ `jev` when `TYPESAFE_API_KEY` is set, else none. `--no-judge` turns it off for one command.
145
+
146
+ Each open source claims the URLs it opens (`opens`): `youtube` a YouTube page, `fetcher` and
147
+ `ladder` any other http(s) page, `reddit` a Reddit thread or comment permalink, `facebook` a
148
+ public group post. A site's own reader (`first`) gets its turn before the general openers,
149
+ whatever the allow-list's order: it answers the thread as data, where a fetched page is a wall.
150
+ `reddit` reads signed out first (`reddit-public`, the thread's feed); only when that misses and
151
+ `BIRDWATCH_ACCOUNTS` names an account does it ask again signed in as it. `facebook` is always
152
+ signed out. A spent cap or a refused read is a miss; a busy profile (503) or no answer retries.
153
+ A refusal carries the worker's access code in its reason: `need_login`, `platform_blocked`,
154
+ `rate_limited` or `upstream_broken`. The last two never read signed in: that fixes neither.
155
+ The read step asks before a call, so a source that was never
156
+ going to open a hit writes no spend. A page that was fetched with nothing usable (a 403, a block
157
+ the ladder's last rung still hit, a media no reader takes, a ladder answer that is not HTML such
158
+ as a PDF) is a miss: the source throws, its `spend` says `ok: false` with the reason, the next
159
+ opener gets its turn, and a hit every opener missed is a `gap` that lists each reason. A page
160
+ that answered 200 and still holds nothing (a captcha wall, a "not found" shell, under 200
161
+ characters of text) is the same miss, with the title or the count as the reason. A run's
162
+ snapshot pins its sources at `plan`: `sources`, the allow-list, which search, open and follow;
163
+ `opens`, the registry's other openers (unless `--sources` was given), which open and follow but
164
+ never search. `open --with <source>` writes a new snapshot that adds one to `opens` (the read
165
+ note names them when hits missed). The
166
+ ladder costs browser time and one of the worker's daily `fetch` bucket per call. Exa's search
167
+ asks for one highlight per result (three sentences, billed per result) as the hit's `snippet`.
168
+
169
+ Every model call is a `spend` record with the model id, the tokens in and out, and `usd` from
170
+ the list price (`src/app/prices.ts`: Claude Haiku, Sonnet and Opus 5.5; the gateway's `free`
171
+ alias is $0; another model keeps its tokens, no price). `run` prints the run's total on stderr
172
+ and as `cost` in its JSON, and the last review's edge (`edge`: next and why) the same way;
173
+ `brief` prints the total as `cost`.
174
+
175
+ `run` exits 2 when the run stopped on a step that gave up (no model, no source, a block):
176
+ `--resume` continues it once the cause is fixed. A failure worth another try (a 429, a 5xx, a
177
+ dropped connection) is tried three times with a pause, every attempt a `spend` record; a step
178
+ that still cannot go on writes no `done`, and `run` waits and resumes it, five times at most,
179
+ before it exits 2. Searches, reads and extracts fan out (four reads or extracts at a time);
180
+ each document's records land as its batch settles, and a step run again skips what its records
181
+ show done. An extract that fails for good (the model's answer would not parse, a 400) stops the
182
+ step with its findings so far on record and exits 2: `extract` again asks only for the
183
+ documents without a verdict. A model answer the output cap cut is not a failure: the whole
184
+ findings before the cut go through the gate and the model is asked again for the rest of the
185
+ document, with those findings known (`./document`'s `Cut`; the `spend` carries both calls' tokens).
186
+
187
+ Between rounds the person reads `brief` (or the agent tells them), keeps or drops findings (`add
188
+ call`), and steers the run with free text (`add steer`). Every `steer` record shows first in the
189
+ brief and in the extract's prompt of the rounds after it. `budget.rounds` is a guard
190
+ rail, not a stop rule: past it, the edge's why says why one more round is worth it.
191
+
192
+ The steps talk in English records. A query every searcher answered with no new hit, and a kept
193
+ hit no source could open (a 403, a block the ladder could not pass), each write a `gap`; so does
194
+ `add gap`. The round's review reads the gaps and a person's keep or drop with its why; the next
195
+ round's plan reads them, and the review's why on `again`, under "The last round left open".
196
+ `add edge back --to <step>` writes a person's edge: the fold takes it like the agent's. A `pattern`
197
+ (`add pattern`) is what several kept findings say together, in the agent's words; its count of
198
+ distinct sites is computed from the findings' proofs, never claimed, and `brief.patterns` lists
199
+ them for the report. A run can be a child (`--parent <run> --why`): its snapshot names the parent, its plan reads
200
+ what the parent and siblings found (recall over those runs), and its budget is the parent's at
201
+ most. The plan file's `runs.depth` (2) is a guard rail: a deeper child warns. Two caps stop: the
202
+ children per parent round (`runs.children`, 6) and the whole tree's reads (`runs.reads`, pinned
203
+ on the root's snapshot as `treeReads`; by default the root's reads × rounds × (children + 1)).
204
+ `open` stops or cuts its hits at the tree's cap; `brief.lineage` and `brief.treeRoom` show the
205
+ ancestors and the room left. A
206
+ stopped child's stop, patterns and dead ends (`add dead`) reach the parent's next plan as
207
+ known lines; a running child is open. A `class` (`add class`) says what kind of source a document is (primary data, survey,
208
+ practitioner, vendor, forum, news, other), with why; its findings take it, and a class on one
209
+ finding overrides it. `brief.patterns[].classes` counts sources per class: distinct sites,
210
+ but each forum thread on its own; `echo` flags a pattern whose classed sources are all vendors,
211
+ and `unchecked` lists its findings no check has read. Only the agent or a person writes a
212
+ class. A tree facet's `needs` names the classes that can prove it; `tree.facets[].lacks` says
213
+ its read pages have none (and the facet is not `enough`), and a facet whose every try pins one site is flagged. A `draft` (`add draft`, JSON items on stdin) is a rough draft over the kept
214
+ findings and patterns: the study's draft gate drops an item that cites nothing kept or has a number
215
+ no cited quote has. `brief.draft` shows the latest. It is never the final write.
216
+
217
+ The check step reads a sample of the round's kept findings (`budget.checks`, 3 by default, or a
218
+ tenth of the kept findings, whichever is more; 0 is none; spread across documents);
219
+ `check --patterns` reads every unchecked finding a pattern rests on, against the text around each quote. The extract gate proves a quote is
220
+ on the page; the check asks whether it backs the sentence. A finding that fails gets a `check`
221
+ record and a drop call; `add call <finding> keep` puts it back. A fail comes with a fix when one
222
+ fits: a narrower sentence the same quote backs. The fix passes the extract gate and a second
223
+ check, then lands as a new finding with `fixes` naming the old one. The review sees every check, and a finding
224
+ carries its document's date, so the review weighs age as the plan file's `age` line says.
225
+
226
+ The judge (Jev, from `@wrenautomation/models/judge`) answers typed questions with probabilities
227
+ and no text, so it never writes a call or a check. `search` asks it of each new hit: does it
228
+ serve the ask, whose side wrote it (the people the ask is about, or a seller to them), what
229
+ class. `open` asks each new page its class. `check` asks each kept finding with a quote: does the
230
+ quote back it, does it credit another source. Each answer is a `judge` record and each call a
231
+ spend. They hint and route: `brief.hits.uncalled` sorts by facets then the judge's serves, and
232
+ shows its answers; `documents.unclassed[].guess` shows its class; a facet with `needs` is not
233
+ enough while its kept hits have a class (the agent's, else the judge's) and none is needed;
234
+ `patterns[].cites` lists findings that credit another source, and the vendor echo warning names
235
+ them. `check` reads the sample plus each finding the judge backs under 0.8: a judge's yes never
236
+ takes one out. `accept` turns the hints into records at the agent's own bar, each with the
237
+ judge's number in its why. `brief.judge` counts its calls, cost, and how often the agent and the
238
+ check agreed with it. A trial (0.22.0): `open` also asks each page its `worth` (would a careful
239
+ researcher cite it for the ask). `documents.toExtract[].worth` shows it; `brief.judge.worth`
240
+ gives its AUC against "a finding on it was kept", and what a gate at a half would skip. Nothing
241
+ gates on it yet.
242
+
243
+ From 0.25.0 the judge reads a page whole: the body to its references (an appendix stays), in
244
+ parts of 64,000 characters, 4 at most, each answer the most yes of any part. Jev's input is
245
+ cheap (about $0.04 per million tokens), so a paper costs about a tenth of a cent. A run's
246
+ criteria (`--criterion name="question"`, on `plan`) are the person's own yes-or-no tests of a
247
+ good source: the judge asks each one on every hit and every page, and the brief shows its odds
248
+ on each hit (serves and each test) as a hint. Their mean sorts `hits.uncalled` and `documents.toExtract`; `brief.judge.criteria` gives each one's AUC against
249
+ "a finding on it was kept", on hits and on pages. A sort and a hint, never a gate.
250
+
251
+ From 0.27.0 the extractor reads the body too (the reference list holds no finding). Each review
252
+ sees every earlier round's new hits and kept findings, and decides when to stop. A paper's
253
+ `follow --as refs` and `--as citers` list what it cites and what cites it. A check (25 papers, Sonnet as reference):
254
+ on the whole page, Jev's criteria odds rank Sonnet's yes over its no with AUC 0.94 to 1.00; on the
255
+ abstract, 0.77 to 1.00.
256
+
257
+ A follow is a query record with `follow: {url, as, document?}` and terms `<as> of <url>`. The
258
+ fetch step sends it to the sources whose `follows` takes it, `follow` lists up to 30, and the
259
+ hits go through triage, the read and the caps like a search's. Each query goes to the allow-list
260
+ as it stood when it was written: a source `follow --with` lets in joins `opens`, so it runs
261
+ follows, never searches. A
262
+ `person` names one author across sites (profile URLs, names they post under); a document is
263
+ theirs when a posts follow from their profile found it, when its URL sits under a profile, or
264
+ when its channel (or its hit's) is one of their names. `brief.people` counts each person's
265
+ documents, kept findings and sites; `brief.unfollowed` lists kept pages no follow started from
266
+ and no person owns.
267
+
268
+ `serve` puts the ledger on 127.0.0.1 for a viewer (the Wren portal's Research page). Read only:
269
+ the CLI stays the one writer. `GET /runs` gives a row per run (ask, parent, round, next step,
270
+ counts, spend). `GET /runs/:run` gives a `RunView` (`v: 1`): the brief, every query with the hits
271
+ it wrote and found again, every hit with its sources, queries, call and judge, every page with its
272
+ class, verdict and person, every finding with its checks, the timeline of decisions and step ends
273
+ with their whys, and spend per source. `GET /runs/:run/documents/:doc` gives a page's stored text.
274
+ `GET /events` sends `event: run` with `{run}` each time a run's files change, and a ping each 25 s;
275
+ the viewer then gets that run again. CORS answers `--origin` (the portal by default) and any
276
+ localhost, and says yes to Chrome's private-network preflight. The types live in
277
+ `src/engine/view.ts`; a host copies them and checks `v`, never imports them.
278
+
279
+ ## Library
280
+
281
+ ```ts
282
+ import { PoliteFetcher, userAgent } from "@wrenautomation/read/fetch";
283
+ import { readPage } from "@wrenautomation/read/text";
284
+
285
+ const fetcher = new PoliteFetcher(userAgent("you@example.com"));
286
+ const res = await fetcher.get(url);
287
+ const page = readPage(res.text, res.url);
288
+ ```
289
+
290
+ The engine, with your own model and sources:
291
+
292
+ ```ts
293
+ import { claimExtractor } from "@wrenautomation/birdwatch/document";
294
+ import { extract, open, planRound, registry, search } from "@wrenautomation/birdwatch/engine";
295
+ import { FilesLedger, ulids } from "@wrenautomation/birdwatch/ledger";
296
+ import { exa, fetcherSource } from "@wrenautomation/birdwatch/sources";
297
+
298
+ const ledger = new FilesLedger("./ledger", "will");
299
+ const sources = registry(exa({ keys }), fetcherSource({ fetcher }));
300
+ const deps = { sources, ledger, model, extractor: claimExtractor, clock: () => new Date(), ids: ulids(), writer: "will" };
301
+ const agent = { from: "ai" as const, by: "my-agent" };
302
+ const { run } = await planRound(deps, { ask: { text: "how do staffing firms win repeat clients", kind: "web" }, planFile, model: model.id });
303
+ await search(deps, run, [{ terms: "staffing repeat client rate", why: "the ask's words" }], agent);
304
+ await open(deps, run, { urls: ["https://a.example/report"], why: "a survey" }, agent);
305
+ await extract(deps, run); // findings, each with its proof: a quote on a page the run read
306
+ ```
307
+
308
+ A host with its own ledger, sources or model mounts
309
+ the same commands: `addEngineCommands(program, { ledger, sources, model, extractor })` from
310
+ `@wrenautomation/birdwatch` (wren's `research` command is these over Postgres).
311
+
312
+ ## Develop
313
+
314
+ ```sh
315
+ pnpm install
316
+ pnpm lint && pnpm typecheck && pnpm test
317
+ ```
318
+
319
+ A `v*` tag publishes to npm (`.github/workflows/release.yml`, trusted publishing: no stored token).
320
+
321
+ License: PolyForm Strict 1.0.0 (`LICENSE.md`).
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { type VideoRead } from "@wrenautomation/read/video";
3
+ /** The read as one markdown document: its facts as front matter, its title, its sections. */
4
+ export declare function renderRead(r: VideoRead): string;
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * `birdwatch`: the engine and its readers from a shell. The readers: `fetch <url>` prints a page
4
+ * as text, `read <url>` a video as one markdown document, `list <target>` what a profile, channel
5
+ * or search holds, `text <file>` an HTML or PDF file as text, `page <file>` an HTML file's title,
6
+ * text and links, `watch <video>` what a video file shows and says. `plans [kind]` prints a stock
7
+ * plan file to tune. Keys come from the environment: COHERE_API_KEY (or COHERE_API_KEY_1..n),
8
+ * GEMINI_API_KEY. The engine's commands are in `engine-cli.ts`: the agent's (`plan`, `search`,
9
+ * `open`, `extract`, `check`, `add`, `brief`), the plan file's (`learn`) and
10
+ * the ledger's (`trace`, `yield`, `sources`). A person's word goes through `add ... --human`.
11
+ */
12
+ import { readFile } from "node:fs/promises";
13
+ import { PoliteFetcher, userAgent } from "@wrenautomation/read/fetch";
14
+ import { htmlText, pdfText, readPage } from "@wrenautomation/read/text";
15
+ import { cohereEye, cohereSpeech, flatPlaylist, frameCounts, geminiVideo, readVideo, readYoutube, renderWatched, watchFile, } from "@wrenautomation/read/video";
16
+ import { Command } from "commander";
17
+ import { AskKind, defaultPlanFile, PLAN_KINDS } from "../engine/plan.js";
18
+ import { addEngineCommands, keys } from "./engine-cli.js";
19
+ const program = new Command()
20
+ .name("birdwatch")
21
+ .description("research from the open web: the engine's steps, its ledger, and its readers");
22
+ program
23
+ .command("fetch <url>")
24
+ .description("a page as text (polite: one request at a time per host, a contact in the UA)")
25
+ .option("--contact <who>", "an email address or URL a site operator could reach you at (sent in the User-Agent)", process.env.BIRDWATCH_CONTACT)
26
+ .option("--json", "title, text, links, mailtos and tels as JSON")
27
+ .action(async (url, o) => {
28
+ const fetcher = new PoliteFetcher(userAgent(o.contact));
29
+ const res = await fetcher.get(url);
30
+ const page = readPage(res.text, res.url);
31
+ if (o.json)
32
+ console.log(JSON.stringify({ status: res.status, url: res.url, ...page }, null, 2));
33
+ else
34
+ console.log(page.title ? `${page.title}\n\n${page.text}` : page.text);
35
+ });
36
+ /** The read as one markdown document: its facts as front matter, its title, its sections. */
37
+ export function renderRead(r) {
38
+ const facts = [
39
+ ["source", `${r.kind}:${r.id}`],
40
+ ["title", r.title],
41
+ ["url", r.url],
42
+ ["channel", r.channel],
43
+ ["uploaded", r.uploaded],
44
+ ["duration", r.duration],
45
+ ["thumbnail", r.thumbnail],
46
+ ];
47
+ const front = facts
48
+ .filter((f) => f[1] !== undefined)
49
+ .map(([k, v]) => `${k}: ${typeof v === "string" ? JSON.stringify(v) : v}`)
50
+ .join("\n");
51
+ return `---\n${front}\n---\n\n# ${r.title}\n\n${r.body}`;
52
+ }
53
+ program
54
+ .command("read <url>")
55
+ .description("a video as one markdown document: YouTube from its captions (plus the screen with GEMINI_API_KEY), any other video by watching it (COHERE_API_KEY)")
56
+ .option("--json", "the read as JSON")
57
+ .option("--ffmpeg <path>", "the ffmpeg binary", "ffmpeg")
58
+ .option("--yt-dlp <cmd>", "the yt-dlp command", "yt-dlp")
59
+ .action(async (url, o) => {
60
+ const video = await geminiVideo(keys(process.env, "GEMINI_API_KEY"));
61
+ let read;
62
+ if (/^https?:\/\/(www\.|m\.)?(youtube\.com|youtu\.be)\//.test(url))
63
+ read = await readYoutube(url, { ytDlp: o.ytDlp, video });
64
+ else {
65
+ const cohereKeys = keys(process.env, "COHERE_API_KEY");
66
+ if (!cohereKeys.length)
67
+ throw new Error("read needs COHERE_API_KEY for a video with no captions");
68
+ read = await readVideo(url, {
69
+ ytDlp: o.ytDlp,
70
+ ffmpeg: o.ffmpeg,
71
+ eye: cohereEye(cohereKeys),
72
+ ear: cohereSpeech(cohereKeys),
73
+ video,
74
+ });
75
+ }
76
+ console.log(o.json ? JSON.stringify(read, null, 2) : renderRead(read));
77
+ });
78
+ program
79
+ .command("list <target>")
80
+ .description("what a profile, channel or search (ytsearch10:<words>) holds, as JSON, no downloads")
81
+ .option("--from <n>", "first entry, 1-based", Number)
82
+ .option("--to <n>", "last entry, inclusive", Number)
83
+ .option("--yt-dlp <cmd>", "the yt-dlp command", "yt-dlp")
84
+ .action(async (target, o) => {
85
+ const range = o.from || o.to ? { start: o.from ?? 1, end: o.to ?? Number.MAX_SAFE_INTEGER } : undefined;
86
+ console.log(JSON.stringify(await flatPlaylist(o.ytDlp, target, range), null, 2));
87
+ });
88
+ program
89
+ .command("text <file>")
90
+ .description("an HTML or PDF file as text")
91
+ .action(async (file) => {
92
+ const bytes = await readFile(file);
93
+ const out = /\.pdf$/i.test(file) ? await pdfText(bytes) : htmlText(bytes.toString("utf8"));
94
+ if (out === null)
95
+ throw new Error(`${file}: no text (scanned, encrypted or broken)`);
96
+ console.log(out);
97
+ });
98
+ program
99
+ .command("page <file>")
100
+ .description("an HTML file's title, text, links, mailtos and tels as JSON")
101
+ .option("--url <url>", "the page's address, to resolve its links")
102
+ .action(async (file, o) => {
103
+ const html = await readFile(file, "utf8");
104
+ console.log(JSON.stringify(readPage(html, o.url ?? `file://${file}`), null, 2));
105
+ });
106
+ program
107
+ .command("watch <video>")
108
+ .description("a video file's speech, on-screen text and cuts")
109
+ .option("--json", "the result as JSON")
110
+ .option("--no-speech", "skip the transcription")
111
+ .option("--language <code>", "spoken language, ISO 639-1", "en")
112
+ .option("--every <s>", "seconds between frames", Number, 2)
113
+ .option("--ffmpeg <path>", "the ffmpeg binary", "ffmpeg")
114
+ .action(async (input, o) => {
115
+ const cohereKeys = keys(process.env, "COHERE_API_KEY");
116
+ if (!cohereKeys.length)
117
+ throw new Error("watch needs COHERE_API_KEY");
118
+ const watched = await watchFile(input, {
119
+ eye: cohereEye(cohereKeys),
120
+ ear: cohereSpeech(cohereKeys),
121
+ video: await geminiVideo(keys(process.env, "GEMINI_API_KEY")),
122
+ ffmpeg: o.ffmpeg,
123
+ speech: o.speech,
124
+ language: o.language,
125
+ everyS: o.every,
126
+ });
127
+ if (o.json)
128
+ console.log(JSON.stringify(watched, null, 2));
129
+ else
130
+ console.log(renderWatched(watched));
131
+ const { read, unread } = frameCounts(watched);
132
+ console.error(`${watched.durationS}s, ${read} frames read, ${unread} not read, ${watched.cuts.length} cuts, ${watched.speech.length} chars of speech`);
133
+ });
134
+ program
135
+ .command("plans [kind]")
136
+ .description("a stock plan file to copy and tune; no kind lists them all")
137
+ .action((kind) => {
138
+ const print = (x) => console.log(JSON.stringify(x, null, 2));
139
+ if (kind)
140
+ print(defaultPlanFile(AskKind.parse(kind)));
141
+ else
142
+ print(PLAN_KINDS.map((k) => defaultPlanFile(k)));
143
+ });
144
+ addEngineCommands(program);
145
+ program.parseAsync(process.argv).catch((err) => {
146
+ console.error(err instanceof Error ? err.message : String(err));
147
+ process.exit(1);
148
+ });
149
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/app/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ import type { Model } from "@wrenautomation/models";
2
+ import { type Judge } from "@wrenautomation/models/judge";
3
+ import type { Command } from "commander";
4
+ import type { Extractor } from "../document/index.js";
5
+ import { costOf, type Sources } from "../engine/index.js";
6
+ import { type AskKind, Criterion } from "../engine/plan.js";
7
+ import { FilesLedger, type Ledger, type Problem } from "../ledger/index.js";
8
+ export { costOf };
9
+ /** `NAME`, then `NAME_1`, `NAME_2`, ... in order: a fleet of keys for one vendor. */
10
+ export declare function keys(printed: NodeJS.ProcessEnv, name: string): string[];
11
+ /** `name=question` lines as criteria; a name the judge already asks, or a repeat, fails. */
12
+ export declare function criteriaOf(lines: readonly string[] | undefined): Criterion[];
13
+ export interface Common {
14
+ ledger: string;
15
+ writer: string;
16
+ }
17
+ /** A ledger a host hands the commands: the contract, and what the files backend adds when it has it. */
18
+ export type HostLedger = Ledger & {
19
+ problems?: Problem[];
20
+ };
21
+ /** What a host swaps in: the files backend, this machine's sources and `--model` are the defaults. */
22
+ export interface EngineCliDeps {
23
+ ledger?(o: Common): HostLedger;
24
+ sources?(o: Common, ledger: HostLedger): Sources;
25
+ /** The model for a `--model` spec; the default is models' `modelFromSpec`. */
26
+ model?(spec: string, printed: NodeJS.ProcessEnv): Model;
27
+ /** The judge for a `--judge` spec; the default is models' `judgeFromSpec`. */
28
+ judge?(spec: string, printed: NodeJS.ProcessEnv): Judge | null;
29
+ extractor?: Extractor;
30
+ }
31
+ /** The sources this machine can run, from the environment. */
32
+ export declare function sourcesHere(printed: NodeJS.ProcessEnv, ledger: FilesLedger, ytDlp: string, warn?: (line: string) => void): Sources;
33
+ /**
34
+ * The run's allow-list: the names wanted that exist here. `opens`: unless the list was given by
35
+ * hand, every other opener here, let in to open and follow, never to search (arxiv searched every
36
+ * web run when it joined the list). A name wanted that this machine lacks is dropped with a line on stderr.
37
+ */
38
+ export declare function allowList(sources: Sources, wanted: readonly string[], byHand: boolean, warn?: (line: string) => void): {
39
+ sources: string[];
40
+ opens: string[];
41
+ };
42
+ /** A kind the engine plans for; anything else is the caller's mistake, said in words. */
43
+ export declare const kindOf: (kind: string) => AskKind;
44
+ export declare function addEngineCommands(program: Command, deps?: EngineCliDeps): void;