fetchkit-agents 0.3.0__tar.gz → 0.4.0__tar.gz

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 (60) hide show
  1. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/PKG-INFO +174 -12
  2. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/README.md +173 -11
  3. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/pyproject.toml +1 -1
  4. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/__init__.py +13 -1
  5. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/cli.py +95 -9
  6. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/collector.py +16 -10
  7. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/config_loader.py +22 -11
  8. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/discovery/__init__.py +2 -1
  9. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/discovery/core.py +27 -1
  10. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/discovery/data/catalog.json +241 -1
  11. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/fetchers/__init__.py +6 -0
  12. fetchkit_agents-0.4.0/src/fetchkit/fetchers/edgar.py +287 -0
  13. fetchkit_agents-0.4.0/src/fetchkit/fetchers/gdelt.py +150 -0
  14. fetchkit_agents-0.4.0/src/fetchkit/fetchers/googlenews.py +145 -0
  15. fetchkit_agents-0.4.0/src/fetchkit/fetchers/manifold.py +110 -0
  16. fetchkit_agents-0.4.0/src/fetchkit/fetchers/polymarket.py +145 -0
  17. fetchkit_agents-0.4.0/src/fetchkit/fetchers/reddit.py +132 -0
  18. fetchkit_agents-0.4.0/src/fetchkit/schemas/collector.py +54 -0
  19. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/schemas/describe.py +6 -0
  20. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/schemas/fetcher.py +218 -2
  21. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/schemas/post.py +6 -0
  22. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit_agents.egg-info/PKG-INFO +174 -12
  23. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit_agents.egg-info/SOURCES.txt +6 -0
  24. fetchkit_agents-0.3.0/src/fetchkit/schemas/collector.py +0 -21
  25. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/LICENSE +0 -0
  26. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/setup.cfg +0 -0
  27. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/__main__.py +0 -0
  28. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/discovery/autodiscover.py +0 -0
  29. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/discovery/catalog.py +0 -0
  30. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/discovery/data/CATALOG.md +0 -0
  31. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/discovery/embedding.py +0 -0
  32. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/discovery/errors.py +0 -0
  33. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/discovery/external.py +0 -0
  34. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/discovery/ranking.py +0 -0
  35. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/discovery/schemas.py +0 -0
  36. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/fetchers/arxiv.py +0 -0
  37. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/fetchers/base.py +0 -0
  38. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/fetchers/bluesky.py +0 -0
  39. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/fetchers/github.py +0 -0
  40. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/fetchers/hackernews.py +0 -0
  41. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/fetchers/lobsters.py +0 -0
  42. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/fetchers/mastodon.py +0 -0
  43. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/fetchers/registry.py +0 -0
  44. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/fetchers/rss.py +0 -0
  45. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/fetchers/stackexchange.py +0 -0
  46. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/fetchers/suggest_registry.py +0 -0
  47. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/http/__init__.py +0 -0
  48. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/http/client.py +0 -0
  49. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/http/rate_limit.py +0 -0
  50. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/http/ssrf.py +0 -0
  51. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/py.typed +0 -0
  52. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/schemas/__init__.py +0 -0
  53. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/schemas/base.py +0 -0
  54. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/schemas/config.py +0 -0
  55. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/utils/__init__.py +0 -0
  56. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit/utils/time.py +0 -0
  57. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit_agents.egg-info/dependency_links.txt +0 -0
  58. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit_agents.egg-info/entry_points.txt +0 -0
  59. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit_agents.egg-info/requires.txt +0 -0
  60. {fetchkit_agents-0.3.0 → fetchkit_agents-0.4.0}/src/fetchkit_agents.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fetchkit-agents
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: A YAML-configured data fetching library for agentic applications.
5
5
  Author-email: mete morris <morrismete@gmail.com>
6
6
  License: MIT
@@ -50,7 +50,8 @@ Dynamic: license-file
50
50
  A YAML-configured data-fetching library for agentic applications.
51
51
 
52
52
  `fetchkit` collects posts and comments from sources (Hacker News, RSS/Atom, arXiv,
53
- GitHub, Lobsters, Stack Exchange, Bluesky, Mastodon) into a single canonical `Post`
53
+ GitHub, Lobsters, Stack Exchange, Bluesky, Mastodon, Reddit, Google News, GDELT,
54
+ SEC EDGAR, Polymarket, Manifold) into a single canonical `Post`
54
55
  model, with de-duplication,
55
56
  deterministic sorting, and a shared HTTP client with retries and rate limiting. It is
56
57
  designed as the data-collection layer for LLM/agent pipelines — feed it configs, get
@@ -59,7 +60,8 @@ back clean typed data.
59
60
  - **YAML-first** configuration with strict validation: unknown or misspelled keys
60
61
  are rejected up front, so a bad config fails loudly instead of silently.
61
62
  - **Builtin fetchers**: Hacker News, RSS/Atom, arXiv, GitHub, Lobsters, Stack Exchange,
62
- Bluesky, and Mastodon all zero-auth.
63
+ Bluesky, Mastodon, Reddit, Google News, GDELT (global news firehose), SEC EDGAR
64
+ (securities filings), and Polymarket/Manifold (prediction markets) — all zero-auth.
63
65
  - **Relative time windows**: say `window: "last 6 hours"` instead of computing
64
66
  timestamps.
65
67
  - **Open `metadata`**: each `Post` carries a `metadata` dict for source-specific
@@ -120,6 +122,20 @@ fetchers:
120
122
  - type: mastodon
121
123
  instance: mastodon.social
122
124
  tag: ai
125
+ - type: edgar
126
+ companies: ["AAPL", "MSFT"]
127
+ forms: ["8-K", "10-Q"]
128
+ - type: reddit
129
+ subreddits: ["news", "wallstreetbets"]
130
+ listing: new
131
+ - type: googlenews
132
+ resource: search
133
+ query: "nvidia earnings when:6h"
134
+ - type: gdelt
135
+ query: '"interest rates"'
136
+ languages: ["english"]
137
+ - type: polymarket
138
+ query: "fed"
123
139
  - type: rss
124
140
  feeds:
125
141
  - url: "https://feeds.bbci.co.uk/news/rss.xml"
@@ -156,33 +172,49 @@ Python. `run` prints **only** JSON to stdout (diagnostics go to stderr), so it p
156
172
  cleanly into `jq`:
157
173
 
158
174
  ```bash
159
- fetchkit run config.yaml # pretty JSON: {"count", "posts", "errors"}
175
+ fetchkit run config.yaml # pretty JSON: {"count", "posts", "sources", "errors"}
160
176
  fetchkit run config.yaml -o out.json # write JSON to a file (stdout stays empty)
161
177
  fetchkit run config.yaml --window "6h" # override the time window at runtime
162
178
  fetchkit run config.yaml --compact # single-line JSON
163
179
  fetchkit run config.yaml --fail-on-error # exit 1 if any source failed
164
- fetchkit validate config.yaml # validate a config without fetching
180
+ echo "$CONFIG_YAML" | fetchkit run - # read the config from stdin
181
+ fetchkit validate config.yaml # validate a config without fetching (also accepts -)
165
182
  fetchkit schema # JSON Schema for every config/output model
166
183
  python -m fetchkit run config.yaml # module form, identical behavior
167
184
  ```
168
185
 
186
+ `fetchkit run --help` and `fetchkit validate --help` print the YAML config format
187
+ inline (top-level keys, a minimal example, and the fetcher type list), so simple
188
+ configs need no schema round-trip.
189
+
169
190
  `fetchkit schema` lets an agent discover what it can fetch — it prints the JSON
170
191
  Schema (with field descriptions) for the top-level config, the shared HTTP
171
192
  settings, every builtin fetcher's typed config, and the canonical `Post` output —
172
- so a config can be written without knowing the YAML format out of band:
193
+ so a config can be written without knowing the YAML format out of band. Pass a
194
+ section name to print just that part instead of the full ~50 KB document:
173
195
 
174
196
  ```bash
175
- fetchkit schema | jq '.fetchers | keys' # ["arxiv","bluesky","github","hackernews","lobsters","mastodon","rss","stackexchange"]
197
+ fetchkit schema | jq '.fetchers | keys' # ["arxiv","bluesky","edgar","gdelt","github","googlenews","hackernews","lobsters","manifold","mastodon","polymarket","reddit","rss","stackexchange"]
198
+ fetchkit schema config # just the top-level config schema
199
+ fetchkit schema rss # just one fetcher's config schema
200
+ fetchkit schema post # just the output Post model
176
201
  fetchkit schema -o schema.json # write to a file; supports --compact too
177
202
  ```
178
203
 
179
- Output shape:
204
+ Output shape — `sources` reports per-fetcher success/failure (one entry per
205
+ enabled fetcher, in config order), so partial failures are visible without
206
+ diffing domains against `posts`. A source can partially fail, e.g. one dead
207
+ feed among many: it keeps its posts, records the error, and reports `ok: false`.
180
208
 
181
209
  ```json
182
210
  {
183
211
  "count": 2,
184
212
  "posts": [ { "id": "...", "source": "rss", "title": "...", "url": "...", "created_at": "..." } ],
185
- "errors": [ { "source": "hackernews", "error": "..." } ]
213
+ "sources": [
214
+ { "source": "rss", "name": null, "ok": false, "posts": 2,
215
+ "errors": ["Failed to fetch feed https://dead.example/feed.xml: ..."] }
216
+ ],
217
+ "errors": [ { "source": "rss", "error": "Failed to fetch feed https://dead.example/feed.xml: ..." } ]
186
218
  }
187
219
  ```
188
220
 
@@ -269,6 +301,14 @@ not the live article stream — for the latter, fetch the feed with the `rss`
269
301
  fetcher and match individual posts. Catalog quality is the main lever on result
270
302
  quality; see `src/fetchkit/discovery/data/CATALOG.md` to extend it.
271
303
 
304
+ Because discovery only *ranks* candidates it already holds, a niche query (a
305
+ specific region, product, or community) can come back filled with generic feeds
306
+ that matched only the query's broad terms. The CLI flags this instead of letting
307
+ it pass: the JSON payload carries `unmatched_terms` (query terms no returned
308
+ feed mentions) plus a `hint`, and the same hint is printed to stderr. When you
309
+ see it, feed discovery better candidates — `--from-urls` with sites from your
310
+ own web search, `fetchkit find-feeds <site-url>`, or `--external`.
311
+
272
312
  ## Discoverability for every source
273
313
 
274
314
  `discover` solves RSS's "which feed URL?" problem. The other sources have a
@@ -285,6 +325,12 @@ fetchkit suggest github --query "language:rust stars:>5000" # popular repos to
285
325
  fetchkit suggest mastodon --instance fosstodon.org # trending hashtags
286
326
  fetchkit suggest bluesky # popular custom feeds
287
327
  fetchkit suggest bluesky --what actors --query "ai" # accounts for an author_feed
328
+ fetchkit suggest edgar --query apple # tickers/CIKs for an edgar config
329
+ fetchkit suggest reddit --query finance # subreddits for a topic
330
+ fetchkit suggest gdelt --query country # GDELT languages/countries/operators
331
+ fetchkit suggest googlenews # Google News topics + query operators
332
+ fetchkit suggest polymarket --query fed # most active prediction markets
333
+ fetchkit suggest manifold --query "rate cut" # Manifold markets for a topic
288
334
  fetchkit suggest rss --query "AI safety news" # delegates to discover()
289
335
  ```
290
336
 
@@ -298,6 +344,12 @@ fetchkit suggest rss --query "AI safety news" # delegates to discov
298
344
  | `stackexchange` | popular tags, or sites (`--what sites`) | `tagged` / `site` |
299
345
  | `bluesky` | popular feeds, or actors (`--what actors`) | `actor` |
300
346
  | `mastodon` | trending hashtags on an instance | `tag` / `instance` |
347
+ | `edgar` | company tickers/CIKs matching a query | `companies` |
348
+ | `reddit` | subreddits matching a query | `subreddits` |
349
+ | `gdelt` | languages, country codes, query operators (static) | `languages` / `countries` / `query` |
350
+ | `googlenews` | topic sections + query operators (static) | `topic` / `query` |
351
+ | `polymarket` | most active live markets (by 24h volume) | `query` |
352
+ | `manifold` | live markets matching a query | `query` |
301
353
 
302
354
  > Most suggesters call the source's live API (all no-auth); `hackernews`/`arxiv`
303
355
  > are static, and `rss` reuses the offline catalog ranker. Network suggesters fail
@@ -319,7 +371,9 @@ Set the time window with **either** a relative `window` **or** both
319
371
  `start_time`/`end_time` — the two are mutually exclusive. **If you specify none of
320
372
  them, the window defaults to the last 24 hours.** `start_time <= end_time` is
321
373
  enforced, and unknown top-level keys are rejected. Per-fetcher
322
- `start_time`/`end_time` default to `None` and **inherit** the global window at runtime.
374
+ `start_time`/`end_time` default to `None` and **inherit** the run window from these
375
+ top-level keys at runtime (there is no `global:` key — the top level *is* the
376
+ global scope).
323
377
 
324
378
  #### Relative windows
325
379
 
@@ -441,6 +495,111 @@ plain text; tags, instance, and visibility are kept in `post.metadata`.
441
495
  | `local` | false | Restrict to statuses originating on this instance |
442
496
  | `max_items`| 50 | 1–200 (paginated, 40/page) |
443
497
 
498
+ ### SEC EDGAR (`type: edgar`)
499
+
500
+ Securities filings from the SEC's free, keyless EDGAR APIs. `resource: filings`
501
+ pulls recent filings per company from `data.sec.gov/submissions` (acceptance
502
+ timestamps are near-real-time — new 8-Ks appear within minutes); `resource: search`
503
+ runs EDGAR full-text search across all filers (date granularity only; the run
504
+ window is passed to the API as a date range). Form type, CIK, accession number,
505
+ and 8-K item codes are kept in `post.metadata`.
506
+
507
+ The SEC's [fair-access policy](https://www.sec.gov/os/accessing-edgar-data) asks
508
+ clients to identify themselves with contact info and stay under 10 req/s — set
509
+ `user_agent` and consider `http.rate_limit_per_host`.
510
+
511
+ | Field | Default | Notes |
512
+ |--------------|----------|--------------------------------------------------------------|
513
+ | `resource` | filings | `filings` (per-company recent) or `search` (full-text) |
514
+ | `companies` | `[]` | Tickers or CIKs — required for `resource: filings` |
515
+ | `forms` | `[]` | Form types to include, e.g. `["8-K", "10-Q"]` (empty = all) |
516
+ | `query` | null | Full-text query — required for `resource: search` |
517
+ | `user_agent` | null | Descriptive UA with contact info, e.g. `myapp me@example.com`|
518
+ | `max_items` | 50 | 1–500 |
519
+
520
+ ### Reddit (`type: reddit`)
521
+
522
+ Reddit's public `.json` endpoints with a descriptive User-Agent. `listing: new`
523
+ surfaces posts seconds after submission (engagement is near zero at that point by
524
+ definition); `hot`/`top` carry engagement at the cost of recency. Score,
525
+ comment count, and upvote ratio are captured as of fetch time. Subreddit, flair,
526
+ and upvote ratio are kept in `post.metadata`.
527
+
528
+ > **Terms note:** unauthenticated access is tolerated by Reddit at low volume
529
+ > (~10 requests/minute per IP) but sits in a gray area of Reddit's Data API
530
+ > terms, which formally expect registered OAuth clients. fetchkit issues one
531
+ > request per fetch; keep polling rates low (`http.rate_limit_per_host`).
532
+ > Reddit also blocks many datacenter/cloud IPs outright (403 regardless of
533
+ > User-Agent), so this fetcher may work from a residential connection but not
534
+ > from CI or a cloud host.
535
+
536
+ | Field | Default | Notes |
537
+ |---------------|---------|-------------------------------------------------------------|
538
+ | `subreddits` | `[]` | Without `r/` prefix; combined into one listing request |
539
+ | `listing` | new | `new`, `hot`, `top`, or `rising` (when `query` unset) |
540
+ | `query` | null | Search; restricted to `subreddits` when both are set |
541
+ | `time_filter` | day | For `listing: top` and searches: `hour`…`all` |
542
+ | `max_items` | 50 | 1–100 (single request) |
543
+
544
+ ### GDELT (`type: gdelt`)
545
+
546
+ The GDELT Project's DOC 2.0 API — machine-reads worldwide news media in 65
547
+ languages, re-indexed every 15 minutes. A zero-auth global news firehose. Hits
548
+ carry URL, title, timestamp, source domain/country/language (kept in
549
+ `post.metadata`); there is no author, body text, or engagement, so those fields
550
+ are `None`. The run window maps to the API's `startdatetime`/`enddatetime`.
551
+
552
+ | Field | Default | Notes |
553
+ |-------------|---------|----------------------------------------------------------------|
554
+ | `query` | — | Required; keywords/"phrases" plus operators (`domain:`, `tone<-5`) |
555
+ | `languages` | `[]` | Source languages, e.g. `["english", "spanish"]` |
556
+ | `countries` | `[]` | Source countries (GDELT/FIPS codes), e.g. `["US", "UK"]` |
557
+ | `max_items` | 75 | 1–250 |
558
+
559
+ ### Google News (`type: googlenews`)
560
+
561
+ Google News' public RSS endpoints: top stories, search, or a topic section.
562
+ Minutes-fresh headlines aggregated from thousands of publishers. The query
563
+ language supports `when:2h` (recency), `site:`, quoted phrases, `-exclusions`,
564
+ and `intitle:`. Links are Google News redirect URLs (they resolve to the
565
+ publisher); the publisher name maps to `author`.
566
+
567
+ | Field | Default | Notes |
568
+ |-------------|---------|----------------------------------------------------------------|
569
+ | `resource` | top | `top`, `search` (needs `query`), or `topic` (needs `topic`) |
570
+ | `query` | null | e.g. `'nvidia earnings when:2h'` |
571
+ | `topic` | null | `WORLD`, `NATION`, `BUSINESS`, `TECHNOLOGY`, `ENTERTAINMENT`, `SPORTS`, `SCIENCE`, `HEALTH` |
572
+ | `language` | en-US | UI language (`hl=`) |
573
+ | `country` | US | Edition (`gl=`/`ceid=`) |
574
+ | `max_items` | 50 | 1–100 |
575
+
576
+ ### Polymarket (`type: polymarket`) and Manifold (`type: manifold`)
577
+
578
+ Prediction markets as posts: the market question is the `title`, and live odds,
579
+ volume, and close date land in `post.metadata` (`odds`/`probability`,
580
+ `volume_24h`, …). Odds moving is often a signal about breaking news before
581
+ articles are written. Both APIs are public read-only, no auth.
582
+
583
+ > **Snapshot semantics:** markets are live state, not timestamped events, so
584
+ > these two fetchers deliberately ignore the run window — an active market
585
+ > created months ago is still current. `created_at` is the market's creation
586
+ > time, which also means snapshot posts sort below newer article-shaped posts
587
+ > in a mixed feed.
588
+
589
+ | Field (`polymarket`) | Default | Notes |
590
+ |----------------------|---------|--------------------------------------------------|
591
+ | `query` | null | Substring filter on the question, applied client-side over the top ~100 markets for the chosen `order` (not a full search) |
592
+ | `order` | volume | `volume` (24h), `liquidity`, or `new` |
593
+ | `closed` | false | Include closed/resolved markets |
594
+ | `max_items` | 50 | 1–100 |
595
+
596
+ | Field (`manifold`) | Default | Notes |
597
+ |--------------------|---------|----------------------------------------------------|
598
+ | `query` | null | Full-text search; omit for most recent markets |
599
+ | `market_filter` | open | `open`, `closed`, `resolved`, `all` (searches) |
600
+ | `sort` | score | `score`, `newest`, `liquidity`, `24-hour-vol` |
601
+ | `max_items` | 50 | 1–100 |
602
+
444
603
  ### HTTP (`http:`)
445
604
 
446
605
  | Field | Default | Notes |
@@ -492,7 +651,8 @@ columns.
492
651
  class Post(BaseModel):
493
652
  id: str # unique within source
494
653
  source: str # "hackernews" | "rss" | "arxiv" | "github" | "lobsters"
495
- # | "stackexchange" | "bluesky" | "mastodon"
654
+ # | "stackexchange" | "bluesky" | "mastodon" | "edgar"
655
+ # | "reddit" | "gdelt" | "googlenews" | "polymarket" | "manifold"
496
656
  title: str | None
497
657
  text: str | None # body / content
498
658
  url: str | None # external link
@@ -510,7 +670,9 @@ sorted descending by `(created_at, id)` for deterministic output.
510
670
 
511
671
  > **`score` is source-relative.** Each source defines it differently — Hacker News
512
672
  > points, Lobsters score, GitHub stars (for `search_repos`), Stack Exchange question
513
- > score, Bluesky likes, Mastodon favourites, and `None` for arXiv and RSS. The values
673
+ > score, Bluesky likes, Mastodon favourites, Reddit score (fuzzed by Reddit, and
674
+ > near zero on brand-new posts), and `None` for arXiv, RSS, Google News, GDELT,
675
+ > EDGAR, and the prediction-market sources. The values
514
676
  > are **not comparable across sources**, so don't rank a mixed
515
677
  > feed by `score` directly. Compare within a single `source`, or use a source-aware
516
678
  > ranking of your own. Output is ordered by recency (`created_at`), not by `score`.
@@ -8,7 +8,8 @@
8
8
  A YAML-configured data-fetching library for agentic applications.
9
9
 
10
10
  `fetchkit` collects posts and comments from sources (Hacker News, RSS/Atom, arXiv,
11
- GitHub, Lobsters, Stack Exchange, Bluesky, Mastodon) into a single canonical `Post`
11
+ GitHub, Lobsters, Stack Exchange, Bluesky, Mastodon, Reddit, Google News, GDELT,
12
+ SEC EDGAR, Polymarket, Manifold) into a single canonical `Post`
12
13
  model, with de-duplication,
13
14
  deterministic sorting, and a shared HTTP client with retries and rate limiting. It is
14
15
  designed as the data-collection layer for LLM/agent pipelines — feed it configs, get
@@ -17,7 +18,8 @@ back clean typed data.
17
18
  - **YAML-first** configuration with strict validation: unknown or misspelled keys
18
19
  are rejected up front, so a bad config fails loudly instead of silently.
19
20
  - **Builtin fetchers**: Hacker News, RSS/Atom, arXiv, GitHub, Lobsters, Stack Exchange,
20
- Bluesky, and Mastodon all zero-auth.
21
+ Bluesky, Mastodon, Reddit, Google News, GDELT (global news firehose), SEC EDGAR
22
+ (securities filings), and Polymarket/Manifold (prediction markets) — all zero-auth.
21
23
  - **Relative time windows**: say `window: "last 6 hours"` instead of computing
22
24
  timestamps.
23
25
  - **Open `metadata`**: each `Post` carries a `metadata` dict for source-specific
@@ -78,6 +80,20 @@ fetchers:
78
80
  - type: mastodon
79
81
  instance: mastodon.social
80
82
  tag: ai
83
+ - type: edgar
84
+ companies: ["AAPL", "MSFT"]
85
+ forms: ["8-K", "10-Q"]
86
+ - type: reddit
87
+ subreddits: ["news", "wallstreetbets"]
88
+ listing: new
89
+ - type: googlenews
90
+ resource: search
91
+ query: "nvidia earnings when:6h"
92
+ - type: gdelt
93
+ query: '"interest rates"'
94
+ languages: ["english"]
95
+ - type: polymarket
96
+ query: "fed"
81
97
  - type: rss
82
98
  feeds:
83
99
  - url: "https://feeds.bbci.co.uk/news/rss.xml"
@@ -114,33 +130,49 @@ Python. `run` prints **only** JSON to stdout (diagnostics go to stderr), so it p
114
130
  cleanly into `jq`:
115
131
 
116
132
  ```bash
117
- fetchkit run config.yaml # pretty JSON: {"count", "posts", "errors"}
133
+ fetchkit run config.yaml # pretty JSON: {"count", "posts", "sources", "errors"}
118
134
  fetchkit run config.yaml -o out.json # write JSON to a file (stdout stays empty)
119
135
  fetchkit run config.yaml --window "6h" # override the time window at runtime
120
136
  fetchkit run config.yaml --compact # single-line JSON
121
137
  fetchkit run config.yaml --fail-on-error # exit 1 if any source failed
122
- fetchkit validate config.yaml # validate a config without fetching
138
+ echo "$CONFIG_YAML" | fetchkit run - # read the config from stdin
139
+ fetchkit validate config.yaml # validate a config without fetching (also accepts -)
123
140
  fetchkit schema # JSON Schema for every config/output model
124
141
  python -m fetchkit run config.yaml # module form, identical behavior
125
142
  ```
126
143
 
144
+ `fetchkit run --help` and `fetchkit validate --help` print the YAML config format
145
+ inline (top-level keys, a minimal example, and the fetcher type list), so simple
146
+ configs need no schema round-trip.
147
+
127
148
  `fetchkit schema` lets an agent discover what it can fetch — it prints the JSON
128
149
  Schema (with field descriptions) for the top-level config, the shared HTTP
129
150
  settings, every builtin fetcher's typed config, and the canonical `Post` output —
130
- so a config can be written without knowing the YAML format out of band:
151
+ so a config can be written without knowing the YAML format out of band. Pass a
152
+ section name to print just that part instead of the full ~50 KB document:
131
153
 
132
154
  ```bash
133
- fetchkit schema | jq '.fetchers | keys' # ["arxiv","bluesky","github","hackernews","lobsters","mastodon","rss","stackexchange"]
155
+ fetchkit schema | jq '.fetchers | keys' # ["arxiv","bluesky","edgar","gdelt","github","googlenews","hackernews","lobsters","manifold","mastodon","polymarket","reddit","rss","stackexchange"]
156
+ fetchkit schema config # just the top-level config schema
157
+ fetchkit schema rss # just one fetcher's config schema
158
+ fetchkit schema post # just the output Post model
134
159
  fetchkit schema -o schema.json # write to a file; supports --compact too
135
160
  ```
136
161
 
137
- Output shape:
162
+ Output shape — `sources` reports per-fetcher success/failure (one entry per
163
+ enabled fetcher, in config order), so partial failures are visible without
164
+ diffing domains against `posts`. A source can partially fail, e.g. one dead
165
+ feed among many: it keeps its posts, records the error, and reports `ok: false`.
138
166
 
139
167
  ```json
140
168
  {
141
169
  "count": 2,
142
170
  "posts": [ { "id": "...", "source": "rss", "title": "...", "url": "...", "created_at": "..." } ],
143
- "errors": [ { "source": "hackernews", "error": "..." } ]
171
+ "sources": [
172
+ { "source": "rss", "name": null, "ok": false, "posts": 2,
173
+ "errors": ["Failed to fetch feed https://dead.example/feed.xml: ..."] }
174
+ ],
175
+ "errors": [ { "source": "rss", "error": "Failed to fetch feed https://dead.example/feed.xml: ..." } ]
144
176
  }
145
177
  ```
146
178
 
@@ -227,6 +259,14 @@ not the live article stream — for the latter, fetch the feed with the `rss`
227
259
  fetcher and match individual posts. Catalog quality is the main lever on result
228
260
  quality; see `src/fetchkit/discovery/data/CATALOG.md` to extend it.
229
261
 
262
+ Because discovery only *ranks* candidates it already holds, a niche query (a
263
+ specific region, product, or community) can come back filled with generic feeds
264
+ that matched only the query's broad terms. The CLI flags this instead of letting
265
+ it pass: the JSON payload carries `unmatched_terms` (query terms no returned
266
+ feed mentions) plus a `hint`, and the same hint is printed to stderr. When you
267
+ see it, feed discovery better candidates — `--from-urls` with sites from your
268
+ own web search, `fetchkit find-feeds <site-url>`, or `--external`.
269
+
230
270
  ## Discoverability for every source
231
271
 
232
272
  `discover` solves RSS's "which feed URL?" problem. The other sources have a
@@ -243,6 +283,12 @@ fetchkit suggest github --query "language:rust stars:>5000" # popular repos to
243
283
  fetchkit suggest mastodon --instance fosstodon.org # trending hashtags
244
284
  fetchkit suggest bluesky # popular custom feeds
245
285
  fetchkit suggest bluesky --what actors --query "ai" # accounts for an author_feed
286
+ fetchkit suggest edgar --query apple # tickers/CIKs for an edgar config
287
+ fetchkit suggest reddit --query finance # subreddits for a topic
288
+ fetchkit suggest gdelt --query country # GDELT languages/countries/operators
289
+ fetchkit suggest googlenews # Google News topics + query operators
290
+ fetchkit suggest polymarket --query fed # most active prediction markets
291
+ fetchkit suggest manifold --query "rate cut" # Manifold markets for a topic
246
292
  fetchkit suggest rss --query "AI safety news" # delegates to discover()
247
293
  ```
248
294
 
@@ -256,6 +302,12 @@ fetchkit suggest rss --query "AI safety news" # delegates to discov
256
302
  | `stackexchange` | popular tags, or sites (`--what sites`) | `tagged` / `site` |
257
303
  | `bluesky` | popular feeds, or actors (`--what actors`) | `actor` |
258
304
  | `mastodon` | trending hashtags on an instance | `tag` / `instance` |
305
+ | `edgar` | company tickers/CIKs matching a query | `companies` |
306
+ | `reddit` | subreddits matching a query | `subreddits` |
307
+ | `gdelt` | languages, country codes, query operators (static) | `languages` / `countries` / `query` |
308
+ | `googlenews` | topic sections + query operators (static) | `topic` / `query` |
309
+ | `polymarket` | most active live markets (by 24h volume) | `query` |
310
+ | `manifold` | live markets matching a query | `query` |
259
311
 
260
312
  > Most suggesters call the source's live API (all no-auth); `hackernews`/`arxiv`
261
313
  > are static, and `rss` reuses the offline catalog ranker. Network suggesters fail
@@ -277,7 +329,9 @@ Set the time window with **either** a relative `window` **or** both
277
329
  `start_time`/`end_time` — the two are mutually exclusive. **If you specify none of
278
330
  them, the window defaults to the last 24 hours.** `start_time <= end_time` is
279
331
  enforced, and unknown top-level keys are rejected. Per-fetcher
280
- `start_time`/`end_time` default to `None` and **inherit** the global window at runtime.
332
+ `start_time`/`end_time` default to `None` and **inherit** the run window from these
333
+ top-level keys at runtime (there is no `global:` key — the top level *is* the
334
+ global scope).
281
335
 
282
336
  #### Relative windows
283
337
 
@@ -399,6 +453,111 @@ plain text; tags, instance, and visibility are kept in `post.metadata`.
399
453
  | `local` | false | Restrict to statuses originating on this instance |
400
454
  | `max_items`| 50 | 1–200 (paginated, 40/page) |
401
455
 
456
+ ### SEC EDGAR (`type: edgar`)
457
+
458
+ Securities filings from the SEC's free, keyless EDGAR APIs. `resource: filings`
459
+ pulls recent filings per company from `data.sec.gov/submissions` (acceptance
460
+ timestamps are near-real-time — new 8-Ks appear within minutes); `resource: search`
461
+ runs EDGAR full-text search across all filers (date granularity only; the run
462
+ window is passed to the API as a date range). Form type, CIK, accession number,
463
+ and 8-K item codes are kept in `post.metadata`.
464
+
465
+ The SEC's [fair-access policy](https://www.sec.gov/os/accessing-edgar-data) asks
466
+ clients to identify themselves with contact info and stay under 10 req/s — set
467
+ `user_agent` and consider `http.rate_limit_per_host`.
468
+
469
+ | Field | Default | Notes |
470
+ |--------------|----------|--------------------------------------------------------------|
471
+ | `resource` | filings | `filings` (per-company recent) or `search` (full-text) |
472
+ | `companies` | `[]` | Tickers or CIKs — required for `resource: filings` |
473
+ | `forms` | `[]` | Form types to include, e.g. `["8-K", "10-Q"]` (empty = all) |
474
+ | `query` | null | Full-text query — required for `resource: search` |
475
+ | `user_agent` | null | Descriptive UA with contact info, e.g. `myapp me@example.com`|
476
+ | `max_items` | 50 | 1–500 |
477
+
478
+ ### Reddit (`type: reddit`)
479
+
480
+ Reddit's public `.json` endpoints with a descriptive User-Agent. `listing: new`
481
+ surfaces posts seconds after submission (engagement is near zero at that point by
482
+ definition); `hot`/`top` carry engagement at the cost of recency. Score,
483
+ comment count, and upvote ratio are captured as of fetch time. Subreddit, flair,
484
+ and upvote ratio are kept in `post.metadata`.
485
+
486
+ > **Terms note:** unauthenticated access is tolerated by Reddit at low volume
487
+ > (~10 requests/minute per IP) but sits in a gray area of Reddit's Data API
488
+ > terms, which formally expect registered OAuth clients. fetchkit issues one
489
+ > request per fetch; keep polling rates low (`http.rate_limit_per_host`).
490
+ > Reddit also blocks many datacenter/cloud IPs outright (403 regardless of
491
+ > User-Agent), so this fetcher may work from a residential connection but not
492
+ > from CI or a cloud host.
493
+
494
+ | Field | Default | Notes |
495
+ |---------------|---------|-------------------------------------------------------------|
496
+ | `subreddits` | `[]` | Without `r/` prefix; combined into one listing request |
497
+ | `listing` | new | `new`, `hot`, `top`, or `rising` (when `query` unset) |
498
+ | `query` | null | Search; restricted to `subreddits` when both are set |
499
+ | `time_filter` | day | For `listing: top` and searches: `hour`…`all` |
500
+ | `max_items` | 50 | 1–100 (single request) |
501
+
502
+ ### GDELT (`type: gdelt`)
503
+
504
+ The GDELT Project's DOC 2.0 API — machine-reads worldwide news media in 65
505
+ languages, re-indexed every 15 minutes. A zero-auth global news firehose. Hits
506
+ carry URL, title, timestamp, source domain/country/language (kept in
507
+ `post.metadata`); there is no author, body text, or engagement, so those fields
508
+ are `None`. The run window maps to the API's `startdatetime`/`enddatetime`.
509
+
510
+ | Field | Default | Notes |
511
+ |-------------|---------|----------------------------------------------------------------|
512
+ | `query` | — | Required; keywords/"phrases" plus operators (`domain:`, `tone<-5`) |
513
+ | `languages` | `[]` | Source languages, e.g. `["english", "spanish"]` |
514
+ | `countries` | `[]` | Source countries (GDELT/FIPS codes), e.g. `["US", "UK"]` |
515
+ | `max_items` | 75 | 1–250 |
516
+
517
+ ### Google News (`type: googlenews`)
518
+
519
+ Google News' public RSS endpoints: top stories, search, or a topic section.
520
+ Minutes-fresh headlines aggregated from thousands of publishers. The query
521
+ language supports `when:2h` (recency), `site:`, quoted phrases, `-exclusions`,
522
+ and `intitle:`. Links are Google News redirect URLs (they resolve to the
523
+ publisher); the publisher name maps to `author`.
524
+
525
+ | Field | Default | Notes |
526
+ |-------------|---------|----------------------------------------------------------------|
527
+ | `resource` | top | `top`, `search` (needs `query`), or `topic` (needs `topic`) |
528
+ | `query` | null | e.g. `'nvidia earnings when:2h'` |
529
+ | `topic` | null | `WORLD`, `NATION`, `BUSINESS`, `TECHNOLOGY`, `ENTERTAINMENT`, `SPORTS`, `SCIENCE`, `HEALTH` |
530
+ | `language` | en-US | UI language (`hl=`) |
531
+ | `country` | US | Edition (`gl=`/`ceid=`) |
532
+ | `max_items` | 50 | 1–100 |
533
+
534
+ ### Polymarket (`type: polymarket`) and Manifold (`type: manifold`)
535
+
536
+ Prediction markets as posts: the market question is the `title`, and live odds,
537
+ volume, and close date land in `post.metadata` (`odds`/`probability`,
538
+ `volume_24h`, …). Odds moving is often a signal about breaking news before
539
+ articles are written. Both APIs are public read-only, no auth.
540
+
541
+ > **Snapshot semantics:** markets are live state, not timestamped events, so
542
+ > these two fetchers deliberately ignore the run window — an active market
543
+ > created months ago is still current. `created_at` is the market's creation
544
+ > time, which also means snapshot posts sort below newer article-shaped posts
545
+ > in a mixed feed.
546
+
547
+ | Field (`polymarket`) | Default | Notes |
548
+ |----------------------|---------|--------------------------------------------------|
549
+ | `query` | null | Substring filter on the question, applied client-side over the top ~100 markets for the chosen `order` (not a full search) |
550
+ | `order` | volume | `volume` (24h), `liquidity`, or `new` |
551
+ | `closed` | false | Include closed/resolved markets |
552
+ | `max_items` | 50 | 1–100 |
553
+
554
+ | Field (`manifold`) | Default | Notes |
555
+ |--------------------|---------|----------------------------------------------------|
556
+ | `query` | null | Full-text search; omit for most recent markets |
557
+ | `market_filter` | open | `open`, `closed`, `resolved`, `all` (searches) |
558
+ | `sort` | score | `score`, `newest`, `liquidity`, `24-hour-vol` |
559
+ | `max_items` | 50 | 1–100 |
560
+
402
561
  ### HTTP (`http:`)
403
562
 
404
563
  | Field | Default | Notes |
@@ -450,7 +609,8 @@ columns.
450
609
  class Post(BaseModel):
451
610
  id: str # unique within source
452
611
  source: str # "hackernews" | "rss" | "arxiv" | "github" | "lobsters"
453
- # | "stackexchange" | "bluesky" | "mastodon"
612
+ # | "stackexchange" | "bluesky" | "mastodon" | "edgar"
613
+ # | "reddit" | "gdelt" | "googlenews" | "polymarket" | "manifold"
454
614
  title: str | None
455
615
  text: str | None # body / content
456
616
  url: str | None # external link
@@ -468,7 +628,9 @@ sorted descending by `(created_at, id)` for deterministic output.
468
628
 
469
629
  > **`score` is source-relative.** Each source defines it differently — Hacker News
470
630
  > points, Lobsters score, GitHub stars (for `search_repos`), Stack Exchange question
471
- > score, Bluesky likes, Mastodon favourites, and `None` for arXiv and RSS. The values
631
+ > score, Bluesky likes, Mastodon favourites, Reddit score (fuzzed by Reddit, and
632
+ > near zero on brand-new posts), and `None` for arXiv, RSS, Google News, GDELT,
633
+ > EDGAR, and the prediction-market sources. The values
472
634
  > are **not comparable across sources**, so don't rank a mixed
473
635
  > feed by `score` directly. Compare within a single `source`, or use a source-aware
474
636
  > ranking of your own. Output is ordered by recency (`created_at`), not by `score`.
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
  # Distribution name on PyPI. The importable package and CLI are still `fetchkit`;
7
7
  # the `fetchkit` PyPI name was already taken by an unrelated project.
8
8
  name = "fetchkit-agents"
9
- version = "0.3.0"
9
+ version = "0.4.0"
10
10
  description = "A YAML-configured data fetching library for agentic applications."
11
11
  readme = "README.md"
12
12
  requires-python = ">=3.10"
@@ -19,7 +19,7 @@ See the README for the full configuration reference and the guide to adding
19
19
  fetchers.
20
20
  """
21
21
 
22
- __version__ = "0.3.0"
22
+ __version__ = "0.4.0"
23
23
 
24
24
  from typing import TYPE_CHECKING, Any
25
25
 
@@ -55,6 +55,12 @@ from fetchkit.schemas.fetcher import (
55
55
  StackExchangeFetchConfig,
56
56
  BlueskyFetchConfig,
57
57
  MastodonFetchConfig,
58
+ EdgarFetchConfig,
59
+ RedditFetchConfig,
60
+ GdeltFetchConfig,
61
+ GoogleNewsFetchConfig,
62
+ PolymarketFetchConfig,
63
+ ManifoldFetchConfig,
58
64
  FetcherConfig,
59
65
  )
60
66
  from fetchkit.schemas.collector import CollectorResult
@@ -121,6 +127,12 @@ __all__ = [
121
127
  "StackExchangeFetchConfig",
122
128
  "BlueskyFetchConfig",
123
129
  "MastodonFetchConfig",
130
+ "EdgarFetchConfig",
131
+ "RedditFetchConfig",
132
+ "GdeltFetchConfig",
133
+ "GoogleNewsFetchConfig",
134
+ "PolymarketFetchConfig",
135
+ "ManifoldFetchConfig",
124
136
  "FetcherConfig",
125
137
  "CollectorResult",
126
138
  "FetchKitConfig",