news: /news feed reader and /rss full-screen reader - #370
Merged
Conversation
Adds two commands, both wired into the CLI, the pit, help and completion: /news headlines across every subscribed feed /news <keyword…> search the news /news <url> read one feed without subscribing /news add … subscribe to a feed, an OPML list, or a named bundle /rss the same headlines as a full-screen reader Subscriptions live in ~/.moshcode/news.opml. OPML rather than a news.json of our own invention: it is the format every reader already speaks, so a list can be exported from an existing reader, dropped in, and taken back out with `/news export`. The defaults and the search are the sources brisk.news and advis0r.com already use in production — Google News top stories and category feeds (brisk's fetch-feed.ts), the PR Newswire and GlobeNewswire wires plus the Google/Bing search pairing (advis0r's providers/news/rss.ts), and the four public OPML lists brisk seeds its publisher table from, offered by name. Querying both search engines is advis0r's reasoning carried over: Google has the coverage, Bing's links carry the publisher URL that unwrapRedirect can decode into something a reader can actually open. All 13 default feeds were fetched and parsed before being committed as defaults. Feed parsing is dependency-free, like the rest of moshcode: one path for RSS 2.0, Atom and RDF, DOCTYPE stripped and entities decoded from a fixed table so no feed can expand one into a file read, and http(s)-only links so none can talk the reader into opening file: or data:. The reader borrows herd-ui's terminal discipline — hand-written alternate screen and SGR mouse reporting, one restore path — and renders from a pure function so a frame can be asserted without a tty. 71 new tests. Four bugs they caught, all fixed here: escaped HTML in a description survived because entities were decoded after tags were stripped; a greedy attribute group swallowed the `/` of a self-closing OPML outline, so the category stack grew without bound; a mouse release decoded to no event and fell through to the key decoder, typing the mouse position into the search box; and an over-long word was never split, so a link wrapped the terminal and tore the frame. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
| ], | ||
| seeAlso: ["rss", "stocks", "crypto"], | ||
| note: "subscriptions live in ~/.moshcode/news.opml (override with MOSHCODE_NEWS_OPML). " | ||
| + "With none, a default set is read instead — `moshcode news sources` lists it. " |
|
|
||
| test("the feed list distinguishes defaults from a real subscription", () => { | ||
| assert.match(renderFeeds(DEFAULT_FEEDS, { usingDefaults: true }), /default feeds · nothing subscribed yet/); | ||
| assert.match(renderFeeds([{ name: "mine", title: "Mine", url: "https://m.example/rss", category: "" }], { file: "/tmp/x.opml" }), /1 feed · \/tmp\/x\.opml/); |
ThreatCrush Security Scan97 finding(s) HIGH/CRITICAL: 5 | MEDIUM: 41 | LOW: 51
…and 47 more. Full results in the Security tab. Snippets are redacted; ThreatCrush never prints matched credential material. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
/newsand/rss, wired into the CLI, the pit,/helpand completion.Where the feeds come from
The defaults and the search are the sources brisk.news and advis0r.com already use in production, not invented ones:
apps/web/src/lib/news/fetch-feed.tsscripts/import-opml-feeds.ts/news add journalists)src/providers/news/rss.tssrc/providers/news/tiers.tsQuerying both search engines is advis0r's reasoning carried over: Google has the coverage, Bing's links carry the publisher URL that
unwrapRedirectdecodes into something a reader can actually open.All 13 default feeds were fetched and parsed before being committed as defaults — RSS, Atom and both wires, every item with a link and a date.
Storage
~/.moshcode/news.opml, override withMOSHCODE_NEWS_OPML. OPML rather than anews.jsonof our own invention: it is what every reader speaks, so a list can be exported from an existing reader, dropped in, and taken back out with/news export. With nothing subscribed the defaults stand in — and say that they are — so/newsworks on a fresh install.Parsing
Dependency-free, like the rest of moshcode. One path for RSS 2.0, Atom and RDF. DOCTYPE stripped and entities decoded from a fixed table, so no feed can expand one into a file read;
http(s)only, so none can talk the reader into openingfile:ordata:.The reader
Borrows
herd-ui.mjs's terminal discipline — hand-written alternate screen and SGR mouse reporting, one restore path so a crash cannot leave a terminal with no cursor. Renders from a pure function, so a frame is asserted in a test without a tty.Tests
71 new, 1495 pass / 0 fail across the suite. Four bugs the tests caught, all fixed here:
<description>survived because entities were decoded after tags were stripped — Google News headlines rendered as<a href=…>/of a self-closing OPML outline, so every feed read as a folder that never closed and the category stack grew without bound🤖 Generated with Claude Code