Skip to main content

News

@svendowideit/newsv2026.10.18.1· MIT· 8d agoBEHAVIORSWORKFLOWSREPORTSACCEPTED WARNINGS
01README

The full news stack: feed discovery, catalog management, preference-aware fetching, LLM story fusion, and static HTML rendering — plus a decoupled feedback server that closes the 👍/👎 loop between the rendered page and the workflows. WHAT IT DOES

Turns a pile of RSS/Atom feeds into a ranked, deduplicated, optionally LLM-fused news digest, rendered as a static HTML page you can read or host. It discovers feeds, keeps a catalog of them, fetches and deduplicates articles, learns your preferences from interested/ignored feedback, and remembers what you have read and seen so those articles are hidden on the next render. Clusters related articles into one story, and — if you point it at an OpenAI-compatible LLM — fuses each cluster into a single factual story with citations. Prefer it over reading feeds by hand or wiring a one-off script: every stage is a swamp method (so it composes, resumes, and is auditable), the fetch is incremental, and the feedback page closes the loop so the next run ranks what you actually read. A dedicated @svendowideit/news-feedback workflow drains that feedback every 30 minutes, so a read/seen article disappears on the next render rather than waiting for the 4-hourly fetch. It is LLM-optional: without an LLM it still fetches, filters, and renders.

INSTALL

swamp extension pull @svendowideit/news

DEPENDENCIES

None required — the fetch, catalog, and rendering stages use only the swamp CLI and the bundled deno binary. Three optional pieces: set an llmModel/llmBaseUrl to enable LLM story fusion (any OpenAI-compatible server); install @svendowideit/systemd-service to run the feedback server as a systemd user service; and install @svendowideit/caddy to reverse-proxy that service to news.<host>.<domain>. Without the optional extensions the matching steps (ensure-feedback-server, ensure-caddy-proxy) are skipped with a log.

RUN

# See every setting, its valid values, its default and its current value.
swamp model @svendowideit/news-reader method run setup local-news

# Fast path — feedback, fetch, dedupe, filter, generate HTML. Also ensures
# the feedback server (when systemd-service is installed) and its Caddy
# route news.<host>.<domain> (when caddy is installed).
swamp workflow run @svendowideit/news-fetch

# Feedback only — drains the feedback queue into preferences. Scheduled
# every 30 minutes (at :15/:45) so a read/seen article is hidden on the
# next page render without waiting for the next fetch. Run it by hand to
# consume feedback immediately.
swamp workflow run @svendowideit/news-feedback

# The full loop — discovery → catalog → fetch → filter → fuse → render.
swamp workflow run @svendowideit/news-full

The model instances (local-news, feed-catalog, feed-discovery, feed-analysis) are auto-registered by the workflows on first run — no manual swamp model create needed.

CONFIGURE

Global arguments on the news-reader instance (all optional):

llmBaseUrl                  OpenAI-compatible LLM base   (default local Ollama)
llmModel                    Model tag for fusion; empty  (default "")
                            disables fusion
llmTemperature              Sampling temperature          (default 0.1)
fusionMinClusterSize        Min articles per fusion       (default 2)
citationRetentionDays       How long citations age out    (default 30)
llmConcurrency              Max concurrent LLM calls      (default 3)
maxFusions                  Cap on LLM calls per step     (default 25)
llmTimeoutSec               Per-call LLM timeout (s)      (default 120)
llmFailureThreshold         Server failures before halt   (default 3)
feedbackServerHost          Interface it binds to          (default 127.0.0.1)
feedbackServerPort          Feedback server port          (default 8765)
feedbackServerServiceName   systemd service name          (default feedback-server)
caddyModelName              Caddy model to read the news  (default "" = auto)
                            host/domain from
caddyRouteHostname          Override the proxied hostname (default "" = news.<host>.<domain>)

The feedback server's bind host/port are these model globals — not workflow inputs — so a scheduled news-fetch run can never rebind an existing service. Set them with swamp model @svendowideit/news-reader method run setup local-news (validates and reports the exact change) then apply with swamp model edit local-news; the matching feed-catalog instance takes feedbackServerHost/feedbackServerPort too. The discovery and analysis models each take their own small global args; see the README.

When @svendowideit/caddy is installed, ensure-caddy-proxy reads the host and domain from the caddy model that already owns this machine (its base domain plus the A record matching hostname) — nothing is hardcoded. It declares the route on its own news-caddy model, which the caddy extension merges with the models it already runs, so the main Caddy is not edited directly. Pin a specific source model with caddyModelName, or the whole hostname with caddyRouteHostname, on the local-news instance.

WHAT IT INSTALLS

Nothing by default. With @svendowideit/systemd-service installed it writes the feedback server as a systemd user service and enables user lingering so the server starts at boot; it binds the local-news feedbackServerHost/feedbackServerPort globals (127.0.0.1:8765 by default, local-only) — set them to expose it on the network, which is safe only behind an authenticating proxy. Rewriting the unit does not restart a running process, so after changing the host/port run stopService then startService. With @svendowideit/caddy installed it additionally creates a news-caddy model and declares a reverse-proxy route from news.<host>.<domain> to the feedback server, letting Caddy issue the certificate and (when a DNS provider is configured) the A record. Without those extensions nothing is installed on the host. The rendered HTML, stories, and feedback queue are written under ~/.swamp/news-pages/.

Verify the route once Caddy is serving it:

swamp model @svendowideit/caddy method run news-caddy audit caddy-status
swamp data get caddy-status audit --json | jq '.content.cadies[] | {target, desiredRoutes, inSync}'

and open https://news../ (the host printed by the ensure-caddy-proxy log line) to read the digest the feedback server serves.

02Behaviors4
@svendowideit/news-readerv2026.10.18.1news_reader.ts

Global Arguments

ArgumentTypeDescription
llmBaseUrl?stringBase URL of an OpenAI-compatible LLM server. The /v1/chat/completions path is used. Defaults to a local Ollama instance.
llmModel?stringLLM model tag used for story fusion (Ollama model, e.g. llama3, mistral, qwen2.5).
llmApiKey?stringOptional API key for LLM servers that require authentication. Ollama typically does not need one.
llmTemperature?numberSampling temperature for fusion LLM calls. Low values keep extraction deterministic.
fusionMinClusterSize?numberMinimum cluster size to trigger LLM fusion. Singletons stay as ordinary summaries unless a story already exists.
citationRetentionDays?numberHow long article citations live before aging out of a story. Core facts always survive.
llmConcurrency?numberMax concurrent LLM requests for fusion steps. Start at 1 and increase if the server supports it.
maxFusions?numberHard cap on the number of LLM fusion calls made within a single fusion step (fuseStories / seedStories / regenStories). Once reached, the remaining clusters are skipped and persisted as-is.
llmTimeoutSec?numberPer-call timeout (seconds) for LLM chat/completions requests. Lower values surface server outages faster.
llmFailureThreshold?numberServer-side LLM failures (outage / timeout / HTTP 5xx) tolerated in a step before the step halts. 1 = fail fast on the first server error. Client-side errors are not counted.
feedbackServerPort?number
feedbackServerHost?string
feedbackServerServiceName?stringsystemd user service name for the feedback server.
feedbackServerScriptPath?stringOverride the path to the bundled feedback-server.ts script. Defaults to the script bundled with this extension.
caddyModelName?stringName of the @svendowideit/caddy model to read the news host's base domain and host record from. Empty (default) auto-discovers the caddy model that owns this machine. Only used when @svendowideit/caddy is installed.
typesafeApiKey?stringTypeSafe API key for Jev-based article category classification. Prefer a vault reference.
typesafeBaseUrl?stringTypeSafe API root for Jev-based article category classification.
typesafeModel?stringTypeSafe System One model for category classification. Defaults to Jev.
categoryThreshold?numberMinimum Jev probability for an article category to be kept (multi-label).
newCategoryThreshold?numberMinimum probability of `needs_new_category` required to ask the LLM for new categories.
caddyRouteHostname?stringOverride the full hostname the news pages are proxied under. Empty (default) derives `news.<host>.<domain>` from the caddy model's base domain and this machine's A record.
fn setup(llmBaseUrl?: string, llmModel?: string, llmApiKey?: string, llmTemperature?: number, fusionMinClusterSize?: number, citationRetentionDays?: number, llmConcurrency?: number, maxFusions?: number, llmTimeoutSec?: number, llmFailureThreshold?: number, feedbackServerHost?: string, feedbackServerPort?: number)
Interactive configuration helper. Run with no inputs to list the model's config params (name, kind, default, current value); pass inputs to validate them, test the LLM settings live, and print the exact `swamp model edit` command to persist only the changed values. NOTE: setup does NOT persist values itself.
ArgumentTypeDescription
llmBaseUrl?stringBase URL of an OpenAI-compatible LLM server (e.g. http://localhost:11434)
llmModel?stringLLM model tag for story fusion. Empty = fusion disabled; set a tag to enable.
llmApiKey?stringOptional API key for LLM servers that require authentication.
llmTemperature?numberSampling temperature (0..2). Low values keep extraction deterministic.
fusionMinClusterSize?numberMinimum cluster size to trigger LLM fusion.
citationRetentionDays?numberHow long article citations live before aging out.
llmConcurrency?numberMax parallel LLM requests.
maxFusions?numberHard cap on LLM fusion calls per step before the step stops and the workflow continues.
llmTimeoutSec?numberPer-call LLM request timeout in seconds.
llmFailureThreshold?numberServer-side LLM failures tolerated in a step before the step halts.
feedbackServerHost?stringInterface the feedback server binds to (127.0.0.1 = local-only).
feedbackServerPort?numberPort the feedback server listens on.
fn cleanupCdata()
Strip CDATA wrappers from existing keywords in snapshots and preferences. Run once to clean up data from before the CDATA-stripping fix.
fn fetch(feeds?: array, maxArticlesPerFeed?: number)
Fetch RSS/Atom feeds and store articles
ArgumentTypeDescription
feeds?arrayRSS/Atom feed URLs to fetch — either string URLs or feed objects from news-feed-catalog
maxArticlesPerFeed?numberMaximum articles to keep per feed (default 25)
fn dedupeArticles()
Group articles by URL, mark duplicates, and annotate primary articles with duplicate source info
fn filterByAge(newsAge?: string)
Filter articles by age and store filtered snapshot for HTML generation
ArgumentTypeDescription
newsAge?stringTime range of news to show (e.g., 2h, 7d, 4w, 1m). Supports h (hours), d (days), w (weeks), m (months). Defaults to 3 days.
fn generate(topN?: number, title?: string)
Generate static HTML report from latest articles, ranked by interest
ArgumentTypeDescription
topN?numberNumber of articles to include in the HTML report (0 = all articles, default 0)
title?stringTitle for the HTML report page
fn generateMobile(topN?: number, title?: string)
Generate the mobile/tablet-optimised news summary page (swipe pagination + in-page article reader iframe) from the latest articles, ranked by interest. Writes to `~/.swamp/news-pages/news-mobile.html` by default.
ArgumentTypeDescription
topN?numberNumber of articles to include in the mobile HTML report (0 = all articles, default 0)
title?stringTitle for the mobile HTML report page
fn feedback(articleId: string, action: enum, source?: string, title?: string, keywords?: array)
Record user feedback on an article (interested or ignored)
ArgumentTypeDescription
articleIdstringArticle ID (hash of URL)
actionenumWhether the user found this article interesting or ignored it
source?stringFeed source name
title?stringArticle title
keywords?arrayKeywords/tags from the article
fn gatherFeedback(serverUrl?: string, batchSize?: number, maxBatches?: number)
Poll the feedback queue HTTP server, import entries into preferences, and delete processed entries
ArgumentTypeDescription
serverUrl?stringURL of the feedback queue HTTP server. Defaults to the model's feedbackServerHost/feedbackServerPort globals.
batchSize?numberNumber of feedback entries to process per batch
maxBatches?numberMaximum number of batches to process in one run
fn gatherPages(serverUrl?: string, batchSize?: number, maxBatches?: number, category?: string)
Poll the pages queue HTTP server, return queued page URLs as a data resource
ArgumentTypeDescription
serverUrl?stringURL of the pages queue HTTP server. Defaults to the model's feedbackServerHost/feedbackServerPort globals.
batchSize?numberNumber of queued pages to process per batch
maxBatches?numberMaximum number of batches to process in one run
category?stringCategory tag applied to each page when adding to the catalog
fn clusterArticles(snapshotName?: string, maxClusterSize?: number)
Conservatively cluster filtered articles into same-story groups (no LLM). Requires a filtered-snapshot resource.
ArgumentTypeDescription
snapshotName?string
maxClusterSize?number
fn seedStories(minClusterSize?: number)
LLM-seed persistent Story objects from the current clusters resource.
ArgumentTypeDescription
minClusterSize?number
fn fuseStories()
LLM-delta pass: absorb new cluster articles into existing stories and persist.
fn regenStories()
Throttled P3 pass: full LLM re-fusion of each story from its citations.
fn renderStories()
Render persistent stories to an inline HTML fragment resource (storiesHtml) and optionally a standalone stories.html page.
fn ensureFeedbackServer(port?: number, host?: string, serviceName?: string, scriptPath?: string)
Idempotently ensure the feedback queue HTTP server is running as a systemd user service. Resolves the bundled feedback-server.ts script path, creates the unit via @svendowideit/systemd-service (if that extension is installed), and starts it. Skips gracefully (with a log) if the systemd-service extension is not installed.
ArgumentTypeDescription
port?numberPort the feedback server listens on (defaults to global feedbackServerPort).
host?stringInterface to bind (defaults to global feedbackServerHost, i.e. 127.0.0.1).
serviceName?stringsystemd user service name (defaults to global feedbackServerServiceName).
scriptPath?stringOverride the path to the feedback-server.ts script (defaults to the bundled script).
fn ensureCaddyProxy(model?: string, proxyModel?: string, hostname?: string, upstream?: string, label?: string)
Idempotently declare a Caddy route that reverse-proxies the feedback server to news.<host>.<domain>, by creating/using a dedicated @svendowideit/caddy model that the caddy extension merges with the models it already runs. The host and domain are read from the installed caddy model that owns this machine. Skips gracefully (with a log) when @svendowideit/caddy is not installed or no single caddy model can be chosen.
ArgumentTypeDescription
model?stringSource @svendowideit/caddy model whose base domain and host record supply the news hostname. Defaults to the global caddyModelName, else auto-discovers the caddy model that owns this machine.
proxyModel?string
hostname?stringFull proxied hostname. Defaults to the global caddyRouteHostname, else news.<host>.<domain> from the source caddy model.
upstream?stringBackend host:port to proxy to. Defaults to the feedback server's bind host/port (wildcard binds map to loopback).
label?string

Resources

snapshot(7d)— Latest fetched feed snapshot
filteredSnapshot(7d)— Age-filtered snapshot for HTML generation
preferences(infinite)— User article preferences and learned keyword weights
pagesQueue(7d)— Page URLs pulled from the pages queue for catalog upsert
stories(infinite)— Persistent fused story objects (survive age-filter windows)
clusters(7d)— Transient story clusters awaiting LLM seed/fuse
categories(infinite)— Persisted Jev news category catalog (baseline + LLM-new)
storiesHtml(7d)— Rendered inline HTML fragment for fused stories
dedupedUrls(7d)— Previously deduped article URLs for incremental dedupe
caddyProxy(infinite)— The Caddy route declaring the news pages hostname

Files

report(text/html)— Static HTML news summary page
@svendowideit/news-feed-catalogv2026.10.15.1feed_catalog.ts

Global Arguments

ArgumentTypeDescription
catalogName?stringCatalog name (for multiple catalogs)
dedupeStalenessDays?numberMax days before a cached feed identity is re-fetched during dedupe. New feeds are always fetched.
feedbackServerHost?stringInterface the feedback server binds to. `127.0.0.1` (default) is local-only.
feedbackServerPort?numberPort the feedback server listens on (default 8765).
fn add(url: string, category?: string, name?: string)
Add a feed URL to the catalog
ArgumentTypeDescription
urlstringRSS/Atom feed URL to add
category?stringCategory tag (e.g., tech, news, programming, podcasting)
name?stringHuman-readable feed name (defaults to hostname)
fn seed(url?: string, category?: string)
Ensure the catalog has at least one feed. If the catalog is empty (or missing), add the default swamp-club feed so the news stack has something to fetch on a fresh install. No-op when the catalog already has feeds.
ArgumentTypeDescription
url?stringFeed URL to add when the catalog is empty
category?stringCategory tag for the seeded feed
fn dedupe()
Fetch each catalog feed, group duplicates by content identity, and mark the less expressive (or second) feed as a duplicate.
fn generateFeedsHtml(title?: string, prefs?: object, snapshot?: object)
Generate a static HTML page listing all feeds in the catalog, grouping each feed with its duplicates.
ArgumentTypeDescription
title?stringPage title
prefs?objectNews-reader preferences used for per-feed seen/read/interested/ignored counts
snapshot?objectNews-reader feed snapshot used to map article IDs to feed sources
fn gatherFeedState(serverUrl?: string, batchSize?: number, maxBatches?: number)
Poll the feedback server's /api/feed endpoint, apply enabled/disabled state to the catalog, and delete processed entries.
ArgumentTypeDescription
serverUrl?stringURL of the feedback queue HTTP server. Defaults to this model's feedbackServerHost/feedbackServerPort globals.
batchSize?numberNumber of feed state entries to process per batch
maxBatches?numberMaximum number of batches to process in one run
fn remove(url: string)
Remove a feed URL from the catalog
ArgumentTypeDescription
urlstringRSS/Atom feed URL to remove
fn list(category?: string, limit?: number)
List feeds in the catalog. If category is omitted, all categories are returned. Use limit=0 for unlimited.
ArgumentTypeDescription
category?stringFilter by category (optional)
limit?numberMaximum feeds to return. Use 0 for unlimited.
fn listCategories()
List all unique categories in the catalog

Resources

catalog(infinite)— Feed catalog (list of RSS/Atom feeds with categories)
categories(infinite)— List of unique categories in the catalog
dedupe(infinite)— Result of the dedupe method
dedupe-cache(infinite)— Cached feed identity data for incremental deduplication

Files

report(text/html)— Generated static HTML pages (e.g. the feeds listing)
@svendowideit/news-feed-discoveryv2026.10.15.1feed_discovery.ts

Global Arguments

ArgumentTypeDescription
newsReaderModelId?stringModel ID of the news-reader instance to read snapshots from (empty = find any)
feedCatalogModelId?stringModel ID of the news-feed-catalog instance to upsert into (empty = find any)
fn discover(maxSitesToCrawl?: number, category?: string, dryRun?: boolean, reCrawlAfterDays?: number, articleUrls?: array, nonFeedUrls?: array)
Discover RSS/Atom feeds from article domains, skipping recently-crawled sites and prioritising the most promising new domains
ArgumentTypeDescription
maxSitesToCrawl?numberMaximum new domains to crawl this run (default 10)
category?stringCategory tag for discovered feeds (default 'discovered')
dryRun?booleanIf true, discover feeds but don't add them to the catalog (default false)
reCrawlAfterDays?numberDomains crawled within this many days are skipped unless their feed is still unknown (default 7)
articleUrls?arrayArticle URLs to crawl for new feeds (strings or {url} objects). When
nonFeedUrls?arrayURLs known to be HTML pages (not feeds) to crawl (strings or {url}

Resources

discovery(30d)— Result of the most recent feed discovery run
crawlLedger(infinite)— Ledger of domains already crawled for feed discovery
@svendowideit/news-feed-analysisv2026.10.15.1feed_analysis.ts

Global Arguments

ArgumentTypeDescription
newsReaderModelId?stringModel ID of the news-reader instance whose pages-current data to read
fn analyzePages(maxPagesPerRun?: number, probeCommonPaths?: boolean, pages?: array)
Analyze pages gathered by the news-reader, discover RSS/Atom feeds in each page, and write a page-discovery-result resource
ArgumentTypeDescription
maxPagesPerRun?numberMaximum pages to analyze this run (default 50)
probeCommonPaths?booleanProbe common feed paths on pages that advertise no feed link (default true)
pages?arrayPage entries gathered by the news-reader's gatherPages method. When

Resources

pageDiscovery(30d)— Result of the most recent page-analysis run
03Workflows5
@svendowideit/news-fetch22d55bc4-a25e-48af-a0ba-6c2cef52f761

Fast-path news workflow — gathers feedback, fetches feeds from the catalog, deduplicates articles, filters by age, and generates the HTML report. Runs every 4 hours. No LLM calls, no catalog maintenance, no feed discovery. When @svendowideit/systemd-service is installed the feedback server is ensured as a systemd user service; when @svendowideit/caddy is installed a Caddy route is declared proxying it to news.<host>.<domain> (the host and domain come from the caddy model that already owns this

mainGather feedback, fetch feeds, filter by age, generate HTML.
1.gather-feedback— Poll the feedback queue server and import any pending feedback entries into preferences.
2.ensure-feedback-server— Idempotently ensure the feedback queue HTTP server runs as a systemd user service (via @svendowideit/systemd-service). Skips with a log if that extension is not installed; no-ops if the service is already running. The bind host/port are read from the local-news feedbackServerHost/ feedbackServerPort globals (falling back to the in-code 127.0.0.1/8765 defaults) — configuration, not per-run workflow inputs — so a scheduled run cannot silently rebind an existing service.
3.ensure-caddy-proxy— When @svendowideit/caddy is installed, declare a Caddy route proxying the feedback server to news.<host>.<domain>, reading the host and domain from the caddy model that already owns this machine. Uses a dedicated news-caddy model that the caddy extension merges with the models it already runs. Skips with a log when caddy is not installed or no single caddy model can be chosen.
4.seed— Ensure the catalog has at least one feed (adds the default swamp-club feed when empty) so a fresh install has something to fetch.
5.fetch— Fetch RSS feeds from the news-feed-catalog (or from --input feeds if provided).
6.dedupe-articles— Group articles by URL, mark duplicates, and annotate primary articles with duplicate source info so the filter step skips them and the HTML shows cross-feed indicators.
7.filter— Filter articles by age (default: last 3 days).
8.generate— Generate HTML report from filtered articles, ranked by preferences, with fused stories rendered inline (from fusion workflow).
9.generate-mobile— Generate the swipe-driven mobile/tablet news page (news-mobile.html).
10.generate-feeds-html— Render a static feeds.html listing all catalog feeds, grouped by duplicates, with per-feed seen/read/interested/ignored article counts and a cross-link back to the news summary.
@svendowideit/news-feedback6a4e3f65-3b48-4af7-8cae-777bc96fa52a

Feedback-only workflow — drains the feedback queue server and imports every pending entry (interested, ignored, seen, read) into the news-reader's preferences, then deletes the processed entries so the queue stays empty. Runs every 30 minutes (offset to :15/:45 so it never overlaps the top-of-hour news-fetch run). Decoupling feedback from the 4-hourly fetch means a read/seen article is hidden on the next page generation without waiting for the next full fetch. There is no fetching, filtering, r

mainDrain the feedback queue into the news-reader's preferences.
1.gather-feedback— Poll the feedback queue server, import interested/ignored/seen/read entries into preferences, and delete the processed entries.
@svendowideit/news-fusionf5d98a8b-5f6e-45f9-a2f6-2938dbb16545

LLM-driven story fusion — clusters filtered articles into same-story groups, fuses them into persistent stories via LLM delta passes, seeds new stories for fresh clusters, and renders stories to an inline HTML fragment. Runs every 12 hours. All steps allow failure — fusion is best-effort enrichment. The news workflow reads the rendered stories HTML from the datastore and includes it in the news page. Steps: cluster → fuse → seed → render

mainCluster articles, fuse into stories, seed new stories, render HTML.
1.cluster— Conservatively group filtered articles into same-story clusters (no LLM).
2.fuse— LLM-delta pass — absorb clustered articles into existing persistent stories.
3.seed— LLM-seed persistent Story objects for fresh clusters with no existing story.
4.render— Render persistent stories to an inline storiesHtml fragment and a standalone stories.html page.
@svendowideit/news-curation27ae2513-825f-4445-b325-97cbb40ed816

Feed catalog maintenance — deduplicates feeds by content identity, gathers feed enable/disable state from the feedback server, discovers new RSS feeds from article URLs, and analyzes queued pages for feed discovery. Runs daily at 3am. All steps allow failure — curation is best-effort maintenance. The news workflow reads the catalog's current state directly from the datastore. Steps: dedupe → gather-feed-state → discover → upsert-feed gather-pages → analyze-pages → upsert-page (independe

mainDeduplicate catalog, gather feed state, discover new feeds, analyze pages.
1.seed— Ensure the catalog has at least one feed (adds the default swamp-club feed when empty) so a fresh install has something to fetch.
2.dedupe— Fetch each catalog feed, group by content identity, and mark duplicate feeds so fetch skips them.
3.gather-feed-state— Poll the feedback server's /api/feed endpoint, apply enabled/disabled state to the catalog, and delete processed entries.
4.discover— Discover new RSS feeds from article URLs.
5.upsert-feed-${{ self.feed.url }}— Add each discovered feed to the news-feed-catalog.
6.gather-pages— Poll the pages queue server and collect queued page URLs into a data resource.
7.analyze-pages— Analyze each gathered page and discover RSS/Atom feeds, writing a page-discovery-result resource.
8.upsert-page-${{ self.feed.url }}— Add each discovered page feed to the news-feed-catalog.
@svendowideit/news-fullf04794eb-33d9-443f-a582-3f12699c54e1

Full news feedback loop — gathers feedback from the queue server, fetches feeds from the catalog, generates an HTML summary ranked by your preferences, then discovers new RSS feeds from the article URLs and upserts them into the catalog for the next run, finally rendering feeds.html (a duplicate-grouped catalog listing). Loop: gather-feedback → dedupe (flag duplicate catalog feeds) → fetch → dedupe-articles (merge duplicate articles by URL) → filter by age (default: last 3 days) → cluster relat

mainGather feedback, fetch feeds, filter by age, generate HTML, discover new feeds for next run.
1.gather-feedback— Poll the feedback queue server and import any pending feedback entries into preferences.
2.seed-catalog— Ensure the catalog has at least one feed (adds the default swamp-club feed when empty) so a fresh install has something to fetch.
3.dedupe— Fetch each catalog feed, group by content identity, and mark duplicate feeds so fetch skips them.
4.gather-feed-state— Poll the feedback server's /api/feed endpoint, apply enabled/disabled state to the catalog, and delete processed entries.
5.fetch— Fetch RSS feeds from the news-feed-catalog (or from --input feeds if provided), skipping feeds marked duplicate by the dedupe step.
6.dedupe-articles— Group articles by URL, mark duplicates, and annotate primary articles with duplicate source info so the filter step skips them and the HTML shows cross-feed indicators.
7.filter— Filter articles by age (default: last 3 days).
8.cluster— Conservatively group filtered articles into same-story clusters (no LLM).
9.fuse— LLM-delta pass — absorb clustered articles into existing persistent stories.
10.seed— LLM-seed persistent Story objects for fresh clusters with no existing story.
11.render— Render persistent stories to an inline storiesHtml fragment and a standalone stories.html page.
12.generate— Generate HTML report from filtered articles, ranked by preferences, with fused stories rendered inline.
13.generate-mobile— Generate the swipe-driven mobile/tablet news page (news-mobile.html).
14.generate-feeds-html— Render a static feeds.html listing all catalog feeds, grouped by duplicates, with per-feed seen/read/interested/ignored article counts and a cross-link back to the news summary.
15.discover— Discover new RSS feeds from article URLs.
16.upsert-feed-${{ self.feed.url }}— Add each discovered feed to the news-feed-catalog.
17.gather-pages— Poll the pages queue server and collect queued page URLs into a data resource.
18.analyze-pages— Analyze each gathered page and discover RSS/Atom feeds, writing a page-discovery-result resource.
19.upsert-page-${{ self.feed.url }}— Add each discovered page feed to the news-feed-catalog.
04Reports1
@svendowideit/news-html-reportmethod
news_html_report.ts

Summary of the generated news HTML page with article count

newssummary
05Stats
A
100 / 100
Downloads
21
Archive size
182.4 KB
  • Has README or module doc2/2earned
  • README has a code example1/1earned
  • README is substantive1/1earned
  • Most symbols documented1/1earned
  • No slow types (deprecated)1/1earned
  • Dependencies pass trust audit2/2earned
  • Has description1/1earned
  • Platform support declared (or universal)2/2earned
  • License2/2earned
  • Verified public repository2/2earned

07Platforms