{"id":47272417,"url":"https://github.com/babarot/oksskolten","last_synced_at":"2026-04-02T13:58:43.529Z","repository":{"id":344199845,"uuid":"1177564412","full_name":"babarot/oksskolten","owner":"babarot","description":"🏔️ The AI-native RSS reader","archived":false,"fork":false,"pushed_at":"2026-03-30T09:25:12.000Z","size":11880,"stargazers_count":329,"open_issues_count":4,"forks_count":19,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-03-30T11:32:53.107Z","etag":null,"topics":["ai","llm","news-reader","react","rss","rss-feed","rss-reader","self-hosted","selfhosted","typescript"],"latest_commit_sha":null,"homepage":"https://oksskolten.com","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/babarot.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":"docs/roadmap.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-10T06:29:20.000Z","updated_at":"2026-03-30T09:25:01.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/babarot/oksskolten","commit_stats":null,"previous_names":["babarot/oksskolten"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/babarot/oksskolten","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/babarot%2Foksskolten","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/babarot%2Foksskolten/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/babarot%2Foksskolten/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/babarot%2Foksskolten/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/babarot","download_url":"https://codeload.github.com/babarot/oksskolten/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/babarot%2Foksskolten/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31307428,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T12:59:32.332Z","status":"ssl_error","status_checked_at":"2026-04-02T12:54:48.875Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","llm","news-reader","react","rss","rss-feed","rss-reader","self-hosted","selfhosted","typescript"],"created_at":"2026-03-15T13:58:14.936Z","updated_at":"2026-04-02T13:58:43.522Z","avatar_url":"https://github.com/babarot.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://assets.babarot.dev/files/2026/03/aeeb41766d888243.png\"\u003e\n    \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"https://assets.babarot.dev/files/2026/03/c11e0ce04f0d06e6.png\"\u003e\n    \u003cimg alt=\"Inbox\" src=\"https://assets.babarot.dev/files/2026/03/c11e0ce04f0d06e6.png\" alt=\"Oksskolten — The AI-native RSS reader\"\u003e\n  \u003c/picture\u003e\n\u003c/p\u003e\n\u003c!--\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./public/og-image.png\" alt=\"Oksskolten — The AI-native RSS reader\" width=\"640\" /\u003e\n\u003c/p\u003e\n--\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/babarot/oksskolten/actions/workflows/test.yaml\"\u003e\u003cimg src=\"https://github.com/babarot/oksskolten/actions/workflows/test.yaml/badge.svg\" alt=\"Tests\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/babarot/oksskolten/actions/workflows/test.yaml\"\u003e\u003cimg src=\"https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/babarot/7de3479899077c477335584e14d51adc/raw/oksskolten-server-coverage.json\" alt=\"Server Coverage\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/babarot/oksskolten/actions/workflows/test.yaml\"\u003e\u003cimg src=\"https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/babarot/7de3479899077c477335584e14d51adc/raw/oksskolten-client-coverage.json\" alt=\"Client Coverage\" /\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cstrong\u003eOksskolten\u003c/strong\u003e \u003cem\u003e(pronounced \"ooks-SKOL-ten\")\u003c/em\u003e — every article, full text, by default.\n\u003c/p\u003e\n\n## Why Oksskolten?\n\nMost RSS readers show what the feed gives you — a title and maybe a summary. Some (like Miniflux and FreshRSS) can fetch full article text, but it's opt-in per feed and requires configuration. Oksskolten does it for every article automatically: it fetches the original article, extracts the full text using Mozilla's Readability + 500 noise-removal patterns, converts it to clean Markdown, and stores it locally. No per-feed toggles, no manual CSS selectors — it just works.\n\nBecause Oksskolten always has the complete text, AI summarization and translation produce meaningful results, full-text search actually covers everything, and you never need to leave the app to read an article.\n\n## See it in action\n\n🕺 Live Demo → [demo.oksskolten.com](https://demo.oksskolten.com)\n\n\u003cp align=\"center\"\u003e\n  \u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"docs/images/screenshots/home-default-dark.png\"\u003e\n    \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"docs/images/screenshots/home-default-light.png\"\u003e\n    \u003cimg alt=\"Home\" src=\"docs/images/screenshots/home-default-dark.png\" width=\"49%\"\u003e\n  \u003c/picture\u003e\n  \u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"docs/images/screenshots/inbox-default-dark.png\"\u003e\n    \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"docs/images/screenshots/inbox-default-light.png\"\u003e\n    \u003cimg alt=\"Inbox\" src=\"docs/images/screenshots/inbox-default-dark.png\" width=\"49%\"\u003e\n  \u003c/picture\u003e\n  \u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"docs/images/screenshots/article-chat-default-dark.png\"\u003e\n    \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"docs/images/screenshots/article-chat-default-light.png\"\u003e\n    \u003cimg alt=\"Article\" src=\"docs/images/screenshots/article-chat-default-dark.png\" width=\"49%\"\u003e\n  \u003c/picture\u003e\n  \u003cpicture\u003e\n    \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"docs/images/screenshots/appearance-default-dark.png\"\u003e\n    \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"docs/images/screenshots/appearance-default-light.png\"\u003e\n    \u003cimg alt=\"Appearance\" src=\"docs/images/screenshots/appearance-default-dark.png\" width=\"49%\"\u003e\n  \u003c/picture\u003e\n\u003c/p\u003e\n\n## Features\n\n- **Full-Text Extraction** — Every article is fetched from its source and processed through Readability + 500 noise-removal patterns. You read complete articles inside Oksskolten, never needing to click through to the original site\n- **AI Summarization \u0026 Translation** — On-demand article processing via Anthropic, Gemini, or OpenAI with SSE streaming. Works on full article text, not RSS excerpts\n- **Interactive Chat** — Multi-turn AI conversations with MCP tooling; search articles, get stats, and ask questions about your feeds\n- **Full-Text Search** — Meilisearch-powered search across your entire article archive\n- **Smart Fetching** — Adaptive per-feed scheduling, conditional HTTP requests (ETag/Last-Modified), content-hash deduplication, exponential backoff, and tracking parameter removal\n- **PWA** — Offline reading, background sync, and add-to-home-screen support\n- **Multi-Auth** — Password, Passkey (WebAuthn), and GitHub OAuth — each independently configurable\n- **Smart Feed Management** — Auto-discovery, CSS selector-based feeds (via RSS Bridge), bot bypass (FlareSolverr), and automatic disabling of dead feeds\n- **Article Clipping** — Save any URL as an article, with full content extraction\n- **Theming** — 14 built-in color themes + custom theme import via JSON, 9 article fonts, 8 code highlighting styles\n- **Single Container** — API, SPA, and cron scheduler all run in one Docker container\n\n## Tech Stack\n\n| Layer | Technology |\n|---|---|\n| Backend | [Node.js 22](https://nodejs.org/) + [Fastify](https://fastify.dev/) |\n| Frontend | [React 19](https://react.dev/) + [Vite](https://vite.dev/) + [Tailwind CSS](https://tailwindcss.com/) + [shadcn/ui](https://ui.shadcn.com/) |\n| Database | [SQLite](https://sqlite.org/) via [libsql](https://github.com/tursodatabase/libsql) (WAL mode) |\n| AI | [Anthropic](https://docs.anthropic.com/) / [Gemini](https://ai.google.dev/) / [OpenAI](https://platform.openai.com/) |\n| Search | [Meilisearch](https://www.meilisearch.com/) |\n| Auth | JWT + [Passkey / WebAuthn](https://webauthn.io/) + GitHub OAuth |\n| Deployment | Docker Compose + [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) |\n\n## Architecture\n\n```mermaid\ngraph TD\n    subgraph host[\"Docker Host\"]\n        subgraph app[\"oksskolten (Node.js 22, port 3000)\"]\n            fastify[\"Fastify API\u003cbr/\u003e/api/*\"]\n            spa[\"SPA static serving\u003cbr/\u003e(Vite build)\"]\n            sqlite[\"SQLite\u003cbr/\u003e(WAL mode)\"]\n            cron[\"node-cron\u003cbr/\u003eFeed fetch every 5 min\"]\n            fetcher[\"Fetcher Pipeline\u003cbr/\u003eRSS parse → Readability\u003cbr/\u003e→ HTML cleaner → Markdown\"]\n            ai[\"AI Provider\u003cbr/\u003eAnthropic / Gemini / OpenAI\"]\n            chat[\"Chat Service\u003cbr/\u003eMCP Server + 4 Adapters\"]\n\n            fastify --\u003e sqlite\n            fastify --- spa\n            fastify --\u003e fetcher\n            fastify --\u003e ai\n            fastify --\u003e chat\n            cron --\u003e fetcher\n            fetcher --\u003e sqlite\n            chat --\u003e sqlite\n            chat --\u003e ai\n        end\n\n        bridge[\"RSS Bridge\u003cbr/\u003e(Docker, port 80)\"]\n        flare[\"FlareSolverr\u003cbr/\u003e(Docker, port 8191)\"]\n        tunnel[\"cloudflared\u003cbr/\u003e(Cloudflare Tunnel)\"]\n    end\n\n    user((\"User\")) -- \"HTTPS\" --\u003e tunnel\n    tunnel --\u003e fastify\n    cron -- \"HTTP fetch\" --\u003e rss((\"RSS Feeds\"))\n    fetcher --\u003e bridge\n    fetcher --\u003e flare\n    ai -- \"API\" --\u003e llm_api((\"Anthropic / Gemini\u003cbr/\u003e/ OpenAI API\"))\n```\n\nEverything runs in a single long-lived process — SQLite needs local disk, and node-cron needs a process that stays alive. This rules out serverless/edge runtimes but keeps the stack simple: one container, no external queues or coordination. For cloud deployment, a small VM or [Fly.io + Turso](docs/guides/deploying-to-fly-io.md) works well.\n\nOksskolten also exposes an MCP server, so Claude Code or any MCP client can search, summarize, and query your article archive without opening the app.\n\n\u003e **What's in a name?** Oksskolten is the highest peak in northern Norway — a mountain of knowledge for your feeds.\n\n### Content Pipeline\n\nUnlike traditional RSS readers that rely on feed-provided summaries, Oksskolten fetches every article directly from its source URL and extracts the full text. This means the reader is self-contained — no need to leave the app to read an article.\n\n1. **Fetch RSS** — Adaptive per-feed scheduling with conditional requests (ETag/Last-Modified/content hash)\n2. **Parse** — RSS/Atom/RDF parsed via feedsmith + fast-xml-parser, tracking parameters stripped\n3. **Fetch Article** — Original article URL fetched directly (with FlareSolverr fallback for bot-protected sites)\n4. **Extract** — Full article content extracted with Readability in isolated Worker Threads\n5. **Clean** — ~500 noise-removal patterns strip ads, nav, sidebars, and tracking elements\n6. **Convert** — HTML converted to Markdown with GFM support\n7. **Enrich** — Language detection, OGP image extraction, excerpt generation\n8. **Index** — Articles indexed in Meilisearch for full-text search\n\n### Smart Fetching\n\nThe feed fetcher minimizes bandwidth and adapts to each feed's behavior, inspired by best practices from FreshRSS, Miniflux, and CommaFeed:\n\n- **3-layer change detection** — HTTP 304 (ETag/Last-Modified) → content hash (SHA-256) → full parse. Unchanged feeds are caught early without parsing XML\n- **Adaptive scheduling** — Each feed gets its own check interval (15min–4h) based on three signals: HTTP `Cache-Control`, RSS `\u003cttl\u003e`, and actual article frequency. Active blogs are checked often; dormant ones back off automatically\n- **Resilient error handling** — Exponential backoff on errors (1h–4h cap), but feeds are never disabled. Rate limits (429/503) respect `Retry-After` headers without counting as errors\n- **URL deduplication** — 60+ tracking parameters (utm_*, fbclid, gclid, etc.) are stripped before duplicate checking, preventing the same article from being inserted twice\n\n## Comparison\n\n| | Oksskolten | [Miniflux](https://github.com/miniflux/v2) | [FreshRSS](https://github.com/FreshRSS/FreshRSS) | [Feedly](https://feedly.com/) |\n|---|---|---|---|---|\n| **Full-text extraction** | Every article, by default | Opt-in per feed | Opt-in per feed | Auto (best-effort) |\n| **Extraction engine** | Readability.js + 500 patterns | Go Readability (~390 lines, ~60 rules) | Manual CSS selectors | Proprietary |\n| **JS-rendered sites** | FlareSolverr | — | — | Enterprise only |\n| **Sites without RSS** | Auto-discovery → RSS Bridge → LLM inference | — | — | Pro+ (25) / Enterprise (100) |\n| **AI summarization** | Built-in (Anthropic/Gemini/OpenAI) | — | — | Pro+ only (Leo) |\n| **AI translation** | Built-in (+ Google Translate, DeepL) | — | — | Enterprise only |\n| **AI chat** | MCP-powered, searches archive | — | — | — |\n| **Search** | Meilisearch (typo-tolerant) | PostgreSQL full-text | SQL LIKE | Pro+ (Power Search) |\n| **Database** | SQLite (embedded, WAL) | PostgreSQL (external) | MySQL/PG/SQLite | SaaS |\n| **Deployment** | Single container | Binary + PostgreSQL | PHP + web server + DB | SaaS |\n| **Offline reading** | PWA with background sync | — | — | Mobile apps only |\n| **Auth** | Password + Passkey/WebAuthn + GitHub OAuth | Password + API key | Password + API key | Google/Apple/social + SAML (Enterprise) |\n| **Themes** | 14 + custom JSON import | Light/Dark | ~10 themes | — |\n| **Language** | Node.js (TypeScript) | Go | PHP | — |\n| **Price** | Free / OSS (AGPL-3.0) | Free / OSS (Apache-2.0) | Free / OSS (AGPL-3.0) | $12.99/mo (Pro+) |\n\nMiniflux and FreshRSS are excellent, mature projects. Oksskolten's focus is different: full-text extraction and AI as first-class defaults, not optional add-ons.\n\n## Development\n\n```bash\ndocker compose up --build   # HMR enabled\n# Frontend: http://localhost:5173\n# Backend:  http://localhost:3000\n\nnpm test                    # Run all tests\nnpm run build               # Production build\n```\n\nOn first startup with an empty database, sample feeds and articles are automatically loaded from the demo seed data (`src/lib/demo/seed/*.json`). This gives you a populated UI to work with immediately. The seed is idempotent — it only runs when no RSS feeds exist in the database. To start with an empty database instead, set `NO_SEED=1`.\n\nSee [`.env.example`](.env.example) for available environment variables. AI provider keys are configured through the Settings UI.\n\n## Deployment\n\nRuns anywhere Docker runs — a home NAS, a Raspberry Pi, or a cloud VM.\n\n### Using pre-built images\n\nPre-built multi-architecture Docker images (amd64/arm64) are published to GHCR on every release:\n\n```bash\ndocker pull ghcr.io/babarot/oksskolten:latest\n```\n\nTo use the pre-built image instead of building locally, edit `compose.prod.yaml` and swap the `build` directive for the commented-out `image` line, then:\n\n```bash\ndocker compose -f compose.yaml -f compose.prod.yaml up -d\n```\n\n### Building locally\n\n```bash\n# Production with Cloudflare Tunnel\ndocker compose -f compose.yaml -f compose.prod.yaml up --build -d\n```\n\nThe production compose file includes a `cloudflared` sidecar that exposes the app via Cloudflare Tunnel — no port forwarding or static IP required.\n\n## License\n\n[AGPL-3.0](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbabarot%2Foksskolten","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbabarot%2Foksskolten","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbabarot%2Foksskolten/lists"}