@pencil-agent/nano-pencil 2.0.0-beta.8 → 2.0.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.
- package/README.md +267 -267
- package/dist/build-meta.json +3 -3
- package/dist/core/export-html/AGENT.md +11 -11
- package/dist/core/export-html/template.css +971 -971
- package/dist/core/export-html/template.html +54 -54
- package/dist/core/extensions-host/index.d.ts +1 -1
- package/dist/core/extensions-host/loader.js +1 -1
- package/dist/core/extensions-host/runner.d.ts +1 -0
- package/dist/core/extensions-host/runner.js +2 -2
- package/dist/core/extensions-host/types.d.ts +17 -22
- package/dist/core/lib/ai/src/types.d.ts +12 -2
- package/dist/core/persona/persona-manager.js +5 -2
- package/dist/core/runtime/agent-session.js +3 -3
- package/dist/core/runtime/extension-core-bindings.d.ts +1 -0
- package/dist/core/runtime/extension-core-bindings.js +2 -2
- package/dist/extensions/builtin/AGENT.md +115 -115
- package/dist/extensions/builtin/browser/AGENT.md +17 -17
- package/dist/extensions/builtin/browser/agent-workspace/agent_helpers.py +12 -12
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/amazon/product-search.md +198 -198
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/archive-org/scraping.md +341 -341
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/arxiv/scraping.md +311 -311
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/arxiv-bulk/scraping.md +333 -333
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/atlas/overview.md +70 -70
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/booking-com/scraping.md +578 -578
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/capterra/scraping.md +440 -440
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/centilebrain/generate-estimates.md +110 -110
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/coingecko/scraping.md +325 -325
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/coinmarketcap/scraping.md +463 -463
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/coursera/scraping.md +360 -360
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/craigslist/scraping.md +390 -390
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/crossref/scraping.md +568 -568
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/dev-to/scraping.md +323 -323
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/duckduckgo/scraping.md +349 -349
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/ebay/scraping.md +435 -435
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/etsy/scraping.md +506 -506
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/eventbrite/scraping.md +363 -363
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/expedia/automation.md +168 -168
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/facebook/groups.md +236 -236
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/facebook/pages.md +295 -295
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/framer/editor.md +108 -108
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/fred/scraping.md +493 -493
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/g2/scraping.md +580 -580
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/genius/scraping.md +511 -511
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/github/repo-actions.md +65 -65
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/github/scraping.md +184 -184
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/glassdoor/scraping.md +543 -543
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/gmail/compose.md +122 -122
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/goodreads/scraping.md +461 -461
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/gutenberg/scraping.md +383 -383
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/hackernews/scraping.md +243 -243
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/howlongtobeat/scraping.md +473 -473
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/imdb/scraping.md +271 -271
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/itch-io/scraping.md +436 -436
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/job-boards/indeed-glassdoor.md +1021 -1021
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/letterboxd/scraping.md +349 -349
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/linkedin/invitation-manager.md +109 -109
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/loom/folder-enumeration.md +170 -170
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/macrotrends/scraping.md +537 -537
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/medium/article-hydration.md +120 -120
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/medium/scraping.md +414 -414
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/metacritic/scraping.md +477 -477
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/musicbrainz/scraping.md +478 -478
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/nasa/scraping.md +339 -339
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/news-aggregation/multi-source.md +205 -205
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/open-library/scraping.md +472 -472
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/openalex/scraping.md +470 -470
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/openstreetmap/scraping.md +490 -490
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/package-registries/npm-pypi.md +478 -478
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/polymarket/scraping.md +234 -234
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/producthunt/scraping.md +307 -307
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/pubmed/scraping.md +421 -421
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/quora/scraping.md +364 -364
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/rawg/scraping.md +352 -352
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/reddit/scraping.md +124 -124
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/rest-countries/scraping.md +233 -233
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/sec-edgar/scraping.md +361 -361
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/shopify-admin/README.md +36 -36
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/shopify-admin/embedded-apps.md +72 -72
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/shopify-admin/knowledge-base.md +109 -109
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/shopify-admin/polaris-inputs.md +137 -137
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/soundcloud/scraping.md +362 -362
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/spotify/scraping.md +339 -339
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/stackoverflow/scraping.md +435 -435
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/steam/scraping.md +575 -575
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/substack/scraping.md +338 -338
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/thetechgeeks/pricing.md +52 -52
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/tiktok/upload.md +107 -107
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/tradingview/scraping.md +309 -309
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/trello/boards-and-lists.md +88 -88
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/trustpilot/scraping.md +375 -375
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/walmart/scraping.md +444 -444
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/wayback-machine/scraping.md +306 -306
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/weather/scraping.md +398 -398
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/wellfound/scraping.md +596 -596
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/world-bank/scraping.md +356 -356
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/xiaohongshu/scraping.md +84 -84
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/youtube/scraping.md +418 -418
- package/dist/extensions/builtin/browser/agent-workspace/domain-skills/zillow/scraping.md +433 -433
- package/dist/extensions/builtin/browser/browser.md +73 -73
- package/dist/extensions/builtin/browser/install.md +142 -142
- package/dist/extensions/builtin/browser/interaction-skills/connection.md +48 -48
- package/dist/extensions/builtin/browser/interaction-skills/cookies.md +3 -3
- package/dist/extensions/builtin/browser/interaction-skills/cross-origin-iframes.md +3 -3
- package/dist/extensions/builtin/browser/interaction-skills/dialogs.md +64 -64
- package/dist/extensions/builtin/browser/interaction-skills/downloads.md +3 -3
- package/dist/extensions/builtin/browser/interaction-skills/drag-and-drop.md +3 -3
- package/dist/extensions/builtin/browser/interaction-skills/dropdowns.md +3 -3
- package/dist/extensions/builtin/browser/interaction-skills/iframes.md +3 -3
- package/dist/extensions/builtin/browser/interaction-skills/network-requests.md +3 -3
- package/dist/extensions/builtin/browser/interaction-skills/print-as-pdf.md +3 -3
- package/dist/extensions/builtin/browser/interaction-skills/profile-sync.md +90 -90
- package/dist/extensions/builtin/browser/interaction-skills/screenshots.md +17 -17
- package/dist/extensions/builtin/browser/interaction-skills/scrolling.md +3 -3
- package/dist/extensions/builtin/browser/interaction-skills/shadow-dom.md +3 -3
- package/dist/extensions/builtin/browser/interaction-skills/tabs.md +69 -69
- package/dist/extensions/builtin/browser/interaction-skills/uploads.md +1 -1
- package/dist/extensions/builtin/browser/interaction-skills/viewport.md +3 -3
- package/dist/extensions/builtin/browser/src/browser_harness/AGENT.md +15 -15
- package/dist/extensions/builtin/browser/src/browser_harness/__init__.py +8 -8
- package/dist/extensions/builtin/browser/src/browser_harness/_ipc.py +90 -90
- package/dist/extensions/builtin/browser/src/browser_harness/admin.py +722 -722
- package/dist/extensions/builtin/browser/src/browser_harness/daemon.py +328 -328
- package/dist/extensions/builtin/browser/src/browser_harness/helpers.py +396 -396
- package/dist/extensions/builtin/browser/src/browser_harness/run.py +103 -103
- package/dist/extensions/builtin/discipline/skills/brainstorming/SKILL.md +33 -33
- package/dist/extensions/builtin/discipline/skills/executing-plans/SKILL.md +25 -25
- package/dist/extensions/builtin/discipline/skills/finishing-development-branch/SKILL.md +25 -25
- package/dist/extensions/builtin/discipline/skills/receiving-code-review/SKILL.md +22 -22
- package/dist/extensions/builtin/discipline/skills/requesting-code-review/SKILL.md +31 -31
- package/dist/extensions/builtin/discipline/skills/systematic-debugging/SKILL.md +28 -28
- package/dist/extensions/builtin/discipline/skills/test-driven-development/SKILL.md +32 -32
- package/dist/extensions/builtin/discipline/skills/using-git-worktrees/SKILL.md +25 -25
- package/dist/extensions/builtin/discipline/skills/verification-before-completion/SKILL.md +27 -27
- package/dist/extensions/builtin/discipline/skills/writing-plans/SKILL.md +26 -26
- package/dist/extensions/builtin/goal/README.md +67 -67
- package/dist/extensions/builtin/goal/goal-controller.d.ts +39 -10
- package/dist/extensions/builtin/goal/goal-controller.js +1 -1
- package/dist/extensions/builtin/goal/goal-format.js +1 -1
- package/dist/extensions/builtin/goal/goal-prompts.d.ts +2 -0
- package/dist/extensions/builtin/goal/goal-prompts.js +5 -4
- package/dist/extensions/builtin/goal/goal-store.js +1 -1
- package/dist/extensions/builtin/goal/index.d.ts +1 -1
- package/dist/extensions/builtin/goal/index.js +10 -7
- package/dist/extensions/builtin/grub/README.md +112 -112
- package/dist/extensions/builtin/link-world/agent-workspace/README.md +16 -16
- package/dist/extensions/builtin/link-world/index.js +6 -6
- package/dist/extensions/builtin/link-world/internet-search/internet-search.md +65 -65
- package/dist/extensions/builtin/link-world/link-world-agent.md +82 -82
- package/dist/extensions/builtin/link-world/linkworld.md +313 -313
- package/dist/extensions/builtin/link-world/{network-routing.md → network-routing/network-routing.md} +67 -67
- package/dist/extensions/builtin/loop/README.md +92 -92
- package/dist/extensions/builtin/mcp/figma-design.md +68 -68
- package/dist/extensions/builtin/mcp/mcp-management.md +85 -85
- package/dist/extensions/builtin/plan/index.js +1 -1
- package/dist/extensions/builtin/recap/AGENT.md +15 -15
- package/dist/extensions/builtin/sal/README.md +72 -72
- package/dist/extensions/builtin/security-audit/README.md +289 -289
- package/dist/extensions/builtin/task/task-store.d.ts +4 -0
- package/dist/extensions/builtin/task/task-store.js +1 -1
- package/dist/extensions/builtin/team/AGENT.md +112 -112
- package/dist/extensions/builtin/team/TESTING.md +299 -299
- package/dist/extensions/builtin/token-save/README.md +56 -56
- package/dist/extensions/optional/AGENT.md +10 -10
- package/dist/index.d.ts +5 -30
- package/dist/index.js +1 -1
- package/dist/models.d.ts +7 -0
- package/dist/models.js +1 -0
- package/dist/modes/interactive/components/footer.js +1 -1
- package/dist/modes/interactive/components/task-status-panel.d.ts +36 -0
- package/dist/modes/interactive/components/task-status-panel.js +1 -0
- package/dist/modes/interactive/controllers/stream-render-controller.d.ts +7 -0
- package/dist/modes/interactive/controllers/stream-render-controller.js +2 -2
- package/dist/modes/interactive/interactive-mode.js +40 -40
- package/dist/modes/interactive/state/interactive-state.d.ts +2 -0
- package/dist/modes/interactive/state/interactive-state.js +1 -1
- package/dist/modes/interactive/theme/dark.json +85 -85
- package/dist/modes/interactive/theme/light.json +84 -84
- package/dist/modes/interactive/theme/theme-schema.json +335 -335
- package/dist/modes/interactive/theme/warm.json +81 -81
- package/dist/node_modules/@pencil-agent/ai/dist/cli.js +0 -0
- package/dist/node_modules/@pencil-agent/ai/dist/models.generated.js +1 -1
- package/dist/node_modules/@pencil-agent/ai/dist/providers/anthropic.js +2 -2
- package/dist/node_modules/@pencil-agent/ai/dist/providers/openai-completions.js +5 -5
- package/dist/node_modules/@pencil-agent/ai/dist/providers/openai-responses.js +1 -1
- package/dist/node_modules/@pencil-agent/ai/dist/stream.js +1 -1
- package/dist/packages/protocol/src/commands.d.ts +33 -0
- package/dist/packages/protocol/src/flags.d.ts +20 -0
- package/dist/packages/protocol/src/hooks.d.ts +17 -0
- package/dist/packages/protocol/src/hooks.js +0 -0
- package/dist/packages/{extension-sdk → protocol}/src/index.d.ts +7 -4
- package/dist/packages/protocol/src/index.js +1 -0
- package/dist/packages/{extension-sdk → protocol}/src/lifecycle.d.ts +15 -27
- package/dist/packages/protocol/src/lifecycle.js +0 -0
- package/dist/packages/{extension-sdk → protocol}/src/tools.d.ts +1 -1
- package/dist/packages/protocol/src/tools.js +0 -0
- package/dist/public-config.d.ts +12 -0
- package/dist/public-config.js +1 -0
- package/dist/runtime.d.ts +9 -0
- package/dist/runtime.js +1 -0
- package/dist/session-compaction.d.ts +7 -0
- package/dist/session-compaction.js +1 -0
- package/dist/session.d.ts +7 -0
- package/dist/session.js +1 -0
- package/dist/skills.d.ts +7 -0
- package/dist/skills.js +1 -0
- package/dist/tools.d.ts +7 -0
- package/dist/tools.js +1 -0
- package/docs/ACP/345/215/217/350/256/256/351/233/206/346/210/220/345/274/200/345/217/221/346/226/207/346/241/243.md +851 -0
- package/docs/SDK-TESTING.md +364 -0
- package/docs/codex-goal-command-impl.md +1055 -1055
- package/docs/codex-goal-vs-grub.md +500 -500
- package/docs/custom-provider.md +27 -27
- package/docs/extensions.md +27 -27
- package/docs/keybindings.md +27 -27
- package/docs/loop /351/207/215/346/236/204/345/256/214/346/210/220/346/200/273/347/273/223.md" +250 -250
- package/docs/loop /351/207/215/346/236/204/345/256/214/346/210/220/346/212/245/345/221/212.md" +122 -122
- package/docs/loop /351/207/215/346/236/204/346/226/271/346/241/210.md" +1222 -1222
- package/docs/loop /351/207/215/346/236/204/346/226/271/346/241/210/345/256/236/347/216/260/346/212/245/345/221/212.md" +158 -158
- package/docs/loop /351/207/215/346/236/204/346/226/271/346/241/210/345/257/271/346/257/224/345/210/206/346/236/220.md" +128 -128
- package/docs/loop /351/207/215/346/236/204/350/256/241/345/210/222.md" +320 -320
- package/docs/loop-usage-examples.md +214 -214
- package/docs/mem-core/346/212/200/346/234/257/346/226/207/346/241/243.md +593 -0
- package/docs/models.md +27 -27
- package/docs/packages.md +27 -27
- package/docs/pi-design-philosophy.md +457 -457
- package/docs/planmode.md +1987 -1987
- package/docs/prompt-templates.md +27 -27
- package/docs/providers.md +27 -27
- package/docs/sdk.md +27 -27
- package/docs/skills.md +27 -27
- package/docs/startup-performance-optimization.md +301 -0
- package/docs/themes.md +27 -27
- package/docs/tui.md +27 -27
- package/docs//350/256/244/347/237/245/345/234/260/345/233/276.md +47 -0
- package/package.json +190 -162
- package/dist/packages/extension-sdk/src/index.js +0 -1
- package/docs/cc-agent-design.md +0 -1297
- package/docs/cc-tui-design.md +0 -1333
- package/docs//345/257/271/346/240/207Claude-Code.md +0 -1775
- /package/dist/packages/{extension-sdk/src/lifecycle.js → protocol/src/commands.js} +0 -0
- /package/dist/packages/{extension-sdk/src/tools.js → protocol/src/flags.js} +0 -0
|
@@ -1,205 +1,205 @@
|
|
|
1
|
-
# News Aggregation — Multi-Source
|
|
2
|
-
|
|
3
|
-
Field-tested against TechCrunch, The Verge, Ars Technica, BBC, Guardian, Wired, NPR, HN, Reuters, CNN, NYT (2026-04-18).
|
|
4
|
-
|
|
5
|
-
## Lead with RSS — fastest and most reliable
|
|
6
|
-
|
|
7
|
-
For every site that has a feed, `http_get` + XML parsing is faster and more reliable than a browser. Use `ThreadPoolExecutor` for parallel fetches.
|
|
8
|
-
|
|
9
|
-
**Confirmed working RSS feeds (tested):**
|
|
10
|
-
|
|
11
|
-
| Source | Feed URL | Format | Items | Fetch time |
|
|
12
|
-
|--------|----------|--------|-------|------------|
|
|
13
|
-
| TechCrunch | `https://techcrunch.com/feed/` | RSS 2.0 | 20 | ~0.08s |
|
|
14
|
-
| Ars Technica | `https://feeds.arstechnica.com/arstechnica/index` | RSS 2.0 | 20 | ~0.10s |
|
|
15
|
-
| BBC News | `http://feeds.bbci.co.uk/news/rss.xml` | RSS 2.0 | 37 | ~0.23s |
|
|
16
|
-
| The Guardian (World) | `https://www.theguardian.com/world/rss` | RSS 2.0 | 45 | ~0.11s |
|
|
17
|
-
| The Guardian (Tech) | `https://www.theguardian.com/technology/rss` | RSS 2.0 | 32 | ~0.25s |
|
|
18
|
-
| Wired | `https://www.wired.com/feed/rss` | RSS 2.0 | 50 | ~0.10s |
|
|
19
|
-
| NPR Top Stories | `https://feeds.npr.org/1001/rss.xml` | RSS 2.0 | 10 | ~0.14s |
|
|
20
|
-
| Hacker News | `https://news.ycombinator.com/rss` | RSS 2.0 | 30 | ~0.16s |
|
|
21
|
-
| CNN Top Stories | `http://rss.cnn.com/rss/cnn_topstories.rss` | RSS 2.0 | 69 | ~0.25s |
|
|
22
|
-
| NYT Homepage | `https://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml` | RSS 2.0 | 23 | ~0.12s |
|
|
23
|
-
| The Verge | `https://www.theverge.com/rss/index.xml` | **Atom** | 10 | ~0.15s |
|
|
24
|
-
|
|
25
|
-
## Parallel fetch pattern (4.3x speedup measured)
|
|
26
|
-
|
|
27
|
-
Sequential fetch of 7 feeds: **0.70s**. Parallel fetch of same 7 feeds: **0.16s** (4.3x speedup).
|
|
28
|
-
|
|
29
|
-
```python
|
|
30
|
-
from concurrent.futures import ThreadPoolExecutor
|
|
31
|
-
import xml.etree.ElementTree as ET
|
|
32
|
-
|
|
33
|
-
RSS_FEEDS = [
|
|
34
|
-
("TechCrunch", "https://techcrunch.com/feed/"),
|
|
35
|
-
("Ars Technica", "https://feeds.arstechnica.com/arstechnica/index"),
|
|
36
|
-
("BBC News", "http://feeds.bbci.co.uk/news/rss.xml"),
|
|
37
|
-
("Guardian World", "https://www.theguardian.com/world/rss"),
|
|
38
|
-
("Wired", "https://www.wired.com/feed/rss"),
|
|
39
|
-
("NPR", "https://feeds.npr.org/1001/rss.xml"),
|
|
40
|
-
("Wired", "https://www.wired.com/feed/rss"),
|
|
41
|
-
]
|
|
42
|
-
|
|
43
|
-
def fetch_rss(name_url):
|
|
44
|
-
name, url = name_url
|
|
45
|
-
xml_data = http_get(url)
|
|
46
|
-
root = ET.fromstring(xml_data)
|
|
47
|
-
items = root.findall('.//item')
|
|
48
|
-
return name, items
|
|
49
|
-
|
|
50
|
-
with ThreadPoolExecutor(max_workers=len(RSS_FEEDS)) as ex:
|
|
51
|
-
results = list(ex.map(fetch_rss, RSS_FEEDS))
|
|
52
|
-
|
|
53
|
-
for name, items in results:
|
|
54
|
-
for item in items[:5]:
|
|
55
|
-
title = item.find('title').text
|
|
56
|
-
link = item.find('link').text
|
|
57
|
-
print(f"[{name}] {title}")
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
## The Verge requires Atom namespace parsing
|
|
61
|
-
|
|
62
|
-
The Verge's feed is Atom format, not RSS 2.0. The naive `.//item` selector returns 0 items. The `title` element uses `type="html"` attribute but its `.text` still contains the plain string.
|
|
63
|
-
|
|
64
|
-
```python
|
|
65
|
-
import xml.etree.ElementTree as ET
|
|
66
|
-
|
|
67
|
-
NS = {'atom': 'http://www.w3.org/2005/Atom'}
|
|
68
|
-
|
|
69
|
-
xml_data = http_get("https://www.theverge.com/rss/index.xml")
|
|
70
|
-
root = ET.fromstring(xml_data)
|
|
71
|
-
entries = root.findall('.//atom:entry', NS) # 10 entries
|
|
72
|
-
|
|
73
|
-
for e in entries:
|
|
74
|
-
title = e.find('atom:title', NS).text
|
|
75
|
-
link = e.find('atom:link', NS).get('href')
|
|
76
|
-
print(title, link)
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
Do NOT use `root.findall('.//{http://www.w3.org/2005/Atom}entry')` with a bare namespace — the explicit `NS` dict approach above is cleaner. Do NOT call `.text` on a `find()` result without checking for `None` first (the naive RSS path hit this on The Verge).
|
|
80
|
-
|
|
81
|
-
## Sites that block http_get entirely
|
|
82
|
-
|
|
83
|
-
**Reuters** returns HTTP 403/Forbidden for all `http_get` calls, even with a real browser `User-Agent` header. Use browser fallback (see below).
|
|
84
|
-
|
|
85
|
-
```
|
|
86
|
-
Reuters: ERROR HTTP Error 401: HTTP Forbidden # with AND without User-Agent
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
Reuters's old RSS feeds (`feeds.reuters.com/reuters/topNews`) resolve to DNS NXDOMAIN — they have been shut down.
|
|
90
|
-
|
|
91
|
-
## Sites that work fine with http_get + User-Agent
|
|
92
|
-
|
|
93
|
-
NYT, Guardian, HN, CNN all return full HTML via `http_get` without issues. The User-Agent header (`Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36`) is not required for these but doesn't hurt.
|
|
94
|
-
|
|
95
|
-
```python
|
|
96
|
-
headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"}
|
|
97
|
-
html = http_get("https://www.nytimes.com", headers=headers) # 1.1MB, works
|
|
98
|
-
html = http_get("https://news.ycombinator.com") # 34KB, works without UA
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
**HN parsing via regex (no HTML parser needed):**
|
|
102
|
-
```python
|
|
103
|
-
import re
|
|
104
|
-
html = http_get("https://news.ycombinator.com")
|
|
105
|
-
stories = re.findall(r'class="titleline"><a href="([^"]+)"[^>]*>([^<]+)<', html)
|
|
106
|
-
# Returns list of (url, title) tuples — 30 stories on the front page
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
## Browser extraction — use when RSS is unavailable
|
|
110
|
-
|
|
111
|
-
### BBC (`bbc.com/news`)
|
|
112
|
-
|
|
113
|
-
No consent banner in headless browser (US region served; GDPR banner only appears for EU IP). Articles use `article h2` selectors.
|
|
114
|
-
|
|
115
|
-
```python
|
|
116
|
-
goto_url("https://www.bbc.com/news")
|
|
117
|
-
wait_for_load()
|
|
118
|
-
wait(2)
|
|
119
|
-
|
|
120
|
-
headlines = js("""
|
|
121
|
-
Array.from(document.querySelectorAll('article h2'))
|
|
122
|
-
.map(h => ({
|
|
123
|
-
title: h.innerText.trim(),
|
|
124
|
-
url: h.closest('a')?.href || h.closest('[href]')?.href ||
|
|
125
|
-
h.parentElement.querySelector('a')?.href
|
|
126
|
-
}))
|
|
127
|
-
.filter(h => h.title.length > 10)
|
|
128
|
-
""")
|
|
129
|
-
# Returns 50+ articles. First one is typically LIVE/breaking.
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
If running from a EU IP and a consent banner appears:
|
|
133
|
-
```python
|
|
134
|
-
accept = js("""
|
|
135
|
-
var btns = Array.from(document.querySelectorAll('button'));
|
|
136
|
-
var btn = btns.find(b => /accept all|agree|continue/i.test(b.innerText));
|
|
137
|
-
if (btn) { btn.click(); return 'clicked: ' + btn.innerText; }
|
|
138
|
-
return 'no banner';
|
|
139
|
-
""")
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
Confirmed: `h3` elements on BBC are site-chrome labels ("The BBC is in multiple languages"), NOT article headlines. Use `article h2` only.
|
|
143
|
-
|
|
144
|
-
### TechCrunch (`techcrunch.com`)
|
|
145
|
-
|
|
146
|
-
`article` and `.post-block` selectors return 0 results — TechCrunch changed their layout. Articles are in `h3` elements.
|
|
147
|
-
|
|
148
|
-
```python
|
|
149
|
-
goto_url("https://techcrunch.com")
|
|
150
|
-
wait_for_load()
|
|
151
|
-
wait(2)
|
|
152
|
-
|
|
153
|
-
articles = js("""
|
|
154
|
-
Array.from(document.querySelectorAll('h3'))
|
|
155
|
-
.map(h => ({
|
|
156
|
-
title: h.innerText?.trim(),
|
|
157
|
-
url: h.closest('a')?.href || h.querySelector('a')?.href ||
|
|
158
|
-
h.parentElement?.querySelector('a')?.href
|
|
159
|
-
}))
|
|
160
|
-
.filter(a => a.title && a.title.length > 20)
|
|
161
|
-
""")
|
|
162
|
-
# Returns ~10 articles. RSS is preferred (20 items, no JS required).
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
RSS is almost always faster for TechCrunch: **0.08s vs 3-5s browser** load. Only fall back to browser if you need paywall/subscriber content.
|
|
166
|
-
|
|
167
|
-
### Reuters (`reuters.com`)
|
|
168
|
-
|
|
169
|
-
`http_get` returns 403. Browser loads but the homepage is heavily JS-rendered with delayed hydration. `h3` selectors only return nav elements after standard `wait_for_load()`. Use `wait(3)` plus scroll:
|
|
170
|
-
|
|
171
|
-
```python
|
|
172
|
-
goto_url("https://www.reuters.com")
|
|
173
|
-
wait_for_load()
|
|
174
|
-
wait(3)
|
|
175
|
-
js("window.scrollTo(0, 500)")
|
|
176
|
-
wait(1)
|
|
177
|
-
# Category links work for topic nav:
|
|
178
|
-
links = js("""
|
|
179
|
-
Array.from(document.querySelectorAll('a[href*="/world/"], a[href*="/technology/"]'))
|
|
180
|
-
.filter(a => a.innerText.trim().length > 20)
|
|
181
|
-
.map(a => ({text: a.innerText.trim(), href: a.href}))
|
|
182
|
-
""")
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
Reuters headlines are best obtained from the Guardian or AP — Reuters no longer has a public RSS and their JS hydration is slow.
|
|
186
|
-
|
|
187
|
-
## Decision tree: which approach to use
|
|
188
|
-
|
|
189
|
-
```
|
|
190
|
-
Does the site have an RSS/Atom feed?
|
|
191
|
-
YES → http_get + XML parse (fastest, ~0.1s per feed)
|
|
192
|
-
- RSS 2.0: root.findall('.//item')
|
|
193
|
-
- Atom: root.findall('.//atom:entry', {'atom': 'http://www.w3.org/2005/Atom'})
|
|
194
|
-
NO → Does http_get return valid HTML (not 403/401/JS shell)?
|
|
195
|
-
YES → http_get + regex/BeautifulSoup (fast, ~0.2-0.3s)
|
|
196
|
-
NO → goto + wait_for_load + wait(2) + js() extraction (slow, 3-8s)
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
## What to skip
|
|
200
|
-
|
|
201
|
-
- **Reuters RSS** — DNS dead (`feeds.reuters.com` is NXDOMAIN)
|
|
202
|
-
- **Reuters http_get** — returns 403 regardless of User-Agent
|
|
203
|
-
- **TechCrunch `article`/`.post-block` selectors** — layout changed, use `h3` instead
|
|
204
|
-
- **BBC `h3` for headlines** — those are site-chrome labels; use `article h2`
|
|
205
|
-
- **The Verge `.//item` selector** — feed is Atom, not RSS; use Atom namespace
|
|
1
|
+
# News Aggregation — Multi-Source
|
|
2
|
+
|
|
3
|
+
Field-tested against TechCrunch, The Verge, Ars Technica, BBC, Guardian, Wired, NPR, HN, Reuters, CNN, NYT (2026-04-18).
|
|
4
|
+
|
|
5
|
+
## Lead with RSS — fastest and most reliable
|
|
6
|
+
|
|
7
|
+
For every site that has a feed, `http_get` + XML parsing is faster and more reliable than a browser. Use `ThreadPoolExecutor` for parallel fetches.
|
|
8
|
+
|
|
9
|
+
**Confirmed working RSS feeds (tested):**
|
|
10
|
+
|
|
11
|
+
| Source | Feed URL | Format | Items | Fetch time |
|
|
12
|
+
|--------|----------|--------|-------|------------|
|
|
13
|
+
| TechCrunch | `https://techcrunch.com/feed/` | RSS 2.0 | 20 | ~0.08s |
|
|
14
|
+
| Ars Technica | `https://feeds.arstechnica.com/arstechnica/index` | RSS 2.0 | 20 | ~0.10s |
|
|
15
|
+
| BBC News | `http://feeds.bbci.co.uk/news/rss.xml` | RSS 2.0 | 37 | ~0.23s |
|
|
16
|
+
| The Guardian (World) | `https://www.theguardian.com/world/rss` | RSS 2.0 | 45 | ~0.11s |
|
|
17
|
+
| The Guardian (Tech) | `https://www.theguardian.com/technology/rss` | RSS 2.0 | 32 | ~0.25s |
|
|
18
|
+
| Wired | `https://www.wired.com/feed/rss` | RSS 2.0 | 50 | ~0.10s |
|
|
19
|
+
| NPR Top Stories | `https://feeds.npr.org/1001/rss.xml` | RSS 2.0 | 10 | ~0.14s |
|
|
20
|
+
| Hacker News | `https://news.ycombinator.com/rss` | RSS 2.0 | 30 | ~0.16s |
|
|
21
|
+
| CNN Top Stories | `http://rss.cnn.com/rss/cnn_topstories.rss` | RSS 2.0 | 69 | ~0.25s |
|
|
22
|
+
| NYT Homepage | `https://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml` | RSS 2.0 | 23 | ~0.12s |
|
|
23
|
+
| The Verge | `https://www.theverge.com/rss/index.xml` | **Atom** | 10 | ~0.15s |
|
|
24
|
+
|
|
25
|
+
## Parallel fetch pattern (4.3x speedup measured)
|
|
26
|
+
|
|
27
|
+
Sequential fetch of 7 feeds: **0.70s**. Parallel fetch of same 7 feeds: **0.16s** (4.3x speedup).
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
31
|
+
import xml.etree.ElementTree as ET
|
|
32
|
+
|
|
33
|
+
RSS_FEEDS = [
|
|
34
|
+
("TechCrunch", "https://techcrunch.com/feed/"),
|
|
35
|
+
("Ars Technica", "https://feeds.arstechnica.com/arstechnica/index"),
|
|
36
|
+
("BBC News", "http://feeds.bbci.co.uk/news/rss.xml"),
|
|
37
|
+
("Guardian World", "https://www.theguardian.com/world/rss"),
|
|
38
|
+
("Wired", "https://www.wired.com/feed/rss"),
|
|
39
|
+
("NPR", "https://feeds.npr.org/1001/rss.xml"),
|
|
40
|
+
("Wired", "https://www.wired.com/feed/rss"),
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
def fetch_rss(name_url):
|
|
44
|
+
name, url = name_url
|
|
45
|
+
xml_data = http_get(url)
|
|
46
|
+
root = ET.fromstring(xml_data)
|
|
47
|
+
items = root.findall('.//item')
|
|
48
|
+
return name, items
|
|
49
|
+
|
|
50
|
+
with ThreadPoolExecutor(max_workers=len(RSS_FEEDS)) as ex:
|
|
51
|
+
results = list(ex.map(fetch_rss, RSS_FEEDS))
|
|
52
|
+
|
|
53
|
+
for name, items in results:
|
|
54
|
+
for item in items[:5]:
|
|
55
|
+
title = item.find('title').text
|
|
56
|
+
link = item.find('link').text
|
|
57
|
+
print(f"[{name}] {title}")
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## The Verge requires Atom namespace parsing
|
|
61
|
+
|
|
62
|
+
The Verge's feed is Atom format, not RSS 2.0. The naive `.//item` selector returns 0 items. The `title` element uses `type="html"` attribute but its `.text` still contains the plain string.
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
import xml.etree.ElementTree as ET
|
|
66
|
+
|
|
67
|
+
NS = {'atom': 'http://www.w3.org/2005/Atom'}
|
|
68
|
+
|
|
69
|
+
xml_data = http_get("https://www.theverge.com/rss/index.xml")
|
|
70
|
+
root = ET.fromstring(xml_data)
|
|
71
|
+
entries = root.findall('.//atom:entry', NS) # 10 entries
|
|
72
|
+
|
|
73
|
+
for e in entries:
|
|
74
|
+
title = e.find('atom:title', NS).text
|
|
75
|
+
link = e.find('atom:link', NS).get('href')
|
|
76
|
+
print(title, link)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Do NOT use `root.findall('.//{http://www.w3.org/2005/Atom}entry')` with a bare namespace — the explicit `NS` dict approach above is cleaner. Do NOT call `.text` on a `find()` result without checking for `None` first (the naive RSS path hit this on The Verge).
|
|
80
|
+
|
|
81
|
+
## Sites that block http_get entirely
|
|
82
|
+
|
|
83
|
+
**Reuters** returns HTTP 403/Forbidden for all `http_get` calls, even with a real browser `User-Agent` header. Use browser fallback (see below).
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
Reuters: ERROR HTTP Error 401: HTTP Forbidden # with AND without User-Agent
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Reuters's old RSS feeds (`feeds.reuters.com/reuters/topNews`) resolve to DNS NXDOMAIN — they have been shut down.
|
|
90
|
+
|
|
91
|
+
## Sites that work fine with http_get + User-Agent
|
|
92
|
+
|
|
93
|
+
NYT, Guardian, HN, CNN all return full HTML via `http_get` without issues. The User-Agent header (`Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36`) is not required for these but doesn't hurt.
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"}
|
|
97
|
+
html = http_get("https://www.nytimes.com", headers=headers) # 1.1MB, works
|
|
98
|
+
html = http_get("https://news.ycombinator.com") # 34KB, works without UA
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**HN parsing via regex (no HTML parser needed):**
|
|
102
|
+
```python
|
|
103
|
+
import re
|
|
104
|
+
html = http_get("https://news.ycombinator.com")
|
|
105
|
+
stories = re.findall(r'class="titleline"><a href="([^"]+)"[^>]*>([^<]+)<', html)
|
|
106
|
+
# Returns list of (url, title) tuples — 30 stories on the front page
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Browser extraction — use when RSS is unavailable
|
|
110
|
+
|
|
111
|
+
### BBC (`bbc.com/news`)
|
|
112
|
+
|
|
113
|
+
No consent banner in headless browser (US region served; GDPR banner only appears for EU IP). Articles use `article h2` selectors.
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
goto_url("https://www.bbc.com/news")
|
|
117
|
+
wait_for_load()
|
|
118
|
+
wait(2)
|
|
119
|
+
|
|
120
|
+
headlines = js("""
|
|
121
|
+
Array.from(document.querySelectorAll('article h2'))
|
|
122
|
+
.map(h => ({
|
|
123
|
+
title: h.innerText.trim(),
|
|
124
|
+
url: h.closest('a')?.href || h.closest('[href]')?.href ||
|
|
125
|
+
h.parentElement.querySelector('a')?.href
|
|
126
|
+
}))
|
|
127
|
+
.filter(h => h.title.length > 10)
|
|
128
|
+
""")
|
|
129
|
+
# Returns 50+ articles. First one is typically LIVE/breaking.
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
If running from a EU IP and a consent banner appears:
|
|
133
|
+
```python
|
|
134
|
+
accept = js("""
|
|
135
|
+
var btns = Array.from(document.querySelectorAll('button'));
|
|
136
|
+
var btn = btns.find(b => /accept all|agree|continue/i.test(b.innerText));
|
|
137
|
+
if (btn) { btn.click(); return 'clicked: ' + btn.innerText; }
|
|
138
|
+
return 'no banner';
|
|
139
|
+
""")
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Confirmed: `h3` elements on BBC are site-chrome labels ("The BBC is in multiple languages"), NOT article headlines. Use `article h2` only.
|
|
143
|
+
|
|
144
|
+
### TechCrunch (`techcrunch.com`)
|
|
145
|
+
|
|
146
|
+
`article` and `.post-block` selectors return 0 results — TechCrunch changed their layout. Articles are in `h3` elements.
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
goto_url("https://techcrunch.com")
|
|
150
|
+
wait_for_load()
|
|
151
|
+
wait(2)
|
|
152
|
+
|
|
153
|
+
articles = js("""
|
|
154
|
+
Array.from(document.querySelectorAll('h3'))
|
|
155
|
+
.map(h => ({
|
|
156
|
+
title: h.innerText?.trim(),
|
|
157
|
+
url: h.closest('a')?.href || h.querySelector('a')?.href ||
|
|
158
|
+
h.parentElement?.querySelector('a')?.href
|
|
159
|
+
}))
|
|
160
|
+
.filter(a => a.title && a.title.length > 20)
|
|
161
|
+
""")
|
|
162
|
+
# Returns ~10 articles. RSS is preferred (20 items, no JS required).
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
RSS is almost always faster for TechCrunch: **0.08s vs 3-5s browser** load. Only fall back to browser if you need paywall/subscriber content.
|
|
166
|
+
|
|
167
|
+
### Reuters (`reuters.com`)
|
|
168
|
+
|
|
169
|
+
`http_get` returns 403. Browser loads but the homepage is heavily JS-rendered with delayed hydration. `h3` selectors only return nav elements after standard `wait_for_load()`. Use `wait(3)` plus scroll:
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
goto_url("https://www.reuters.com")
|
|
173
|
+
wait_for_load()
|
|
174
|
+
wait(3)
|
|
175
|
+
js("window.scrollTo(0, 500)")
|
|
176
|
+
wait(1)
|
|
177
|
+
# Category links work for topic nav:
|
|
178
|
+
links = js("""
|
|
179
|
+
Array.from(document.querySelectorAll('a[href*="/world/"], a[href*="/technology/"]'))
|
|
180
|
+
.filter(a => a.innerText.trim().length > 20)
|
|
181
|
+
.map(a => ({text: a.innerText.trim(), href: a.href}))
|
|
182
|
+
""")
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Reuters headlines are best obtained from the Guardian or AP — Reuters no longer has a public RSS and their JS hydration is slow.
|
|
186
|
+
|
|
187
|
+
## Decision tree: which approach to use
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
Does the site have an RSS/Atom feed?
|
|
191
|
+
YES → http_get + XML parse (fastest, ~0.1s per feed)
|
|
192
|
+
- RSS 2.0: root.findall('.//item')
|
|
193
|
+
- Atom: root.findall('.//atom:entry', {'atom': 'http://www.w3.org/2005/Atom'})
|
|
194
|
+
NO → Does http_get return valid HTML (not 403/401/JS shell)?
|
|
195
|
+
YES → http_get + regex/BeautifulSoup (fast, ~0.2-0.3s)
|
|
196
|
+
NO → goto + wait_for_load + wait(2) + js() extraction (slow, 3-8s)
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## What to skip
|
|
200
|
+
|
|
201
|
+
- **Reuters RSS** — DNS dead (`feeds.reuters.com` is NXDOMAIN)
|
|
202
|
+
- **Reuters http_get** — returns 403 regardless of User-Agent
|
|
203
|
+
- **TechCrunch `article`/`.post-block` selectors** — layout changed, use `h3` instead
|
|
204
|
+
- **BBC `h3` for headlines** — those are site-chrome labels; use `article h2`
|
|
205
|
+
- **The Verge `.//item` selector** — feed is Atom, not RSS; use Atom namespace
|