Skip to content

chrismessina/raycast-reader

Repository files navigation

Reader Mode

Read the web distraction-free in Raycast.

Features

  • Clean Reading Experience - Extracts article content and removes distractions
  • AI Summaries - Multiple summary styles powered by Raycast AI
  • Browser Extension Fallback - Access blocked pages and re-import member-only content via the Raycast browser extension
  • Smart URL Detection - Automatically detects URLs from arguments, clipboard, selection, or active browser tab

Commands

Command Description Default
Open in Reader Mode Main command - opens a URL in reader mode Enabled
Open Clipboard in Reader Mode Opens URL from clipboard directly Disabled
Open Current Tab in Reader Mode Opens the current browser tab in reader mode Disabled

The clipboard and current tab commands are disabled by default to reduce command clutter. Enable them in Raycast Settings → Extensions → Reader Mode if you prefer dedicated commands over the main command's auto-detection.

Architecture

Content Extraction

Reader Mode uses a multi-layered approach to extract clean article content:

  1. Site-Specific Extractors (src/extractors/) - Custom extraction logic for complex sites
  2. Site Configuration (src/config/site-config.ts) - Selector-based configuration for simpler sites
  3. Mozilla Readability - Fallback for all other sites

Extractors vs Site Config

Approach Use Case Examples
Extractors Sites needing custom DOM traversal and content transformation Hacker News, GitHub, Reddit
Site Config Sites needing only CSS selector adjustments Medium, Substack, news sites
Readability Standard article pages Most blogs and news articles

Adding Support for New Sites

For simple sites (just need different selectors), add to src/config/site-config.ts:

[
  /^example\.com$/i,
  {
    name: "Example",
    articleSelector: ".article-body",
    removeSelectors: [".ads", ".sidebar"],
  },
],

For complex sites (need custom extraction logic), create a new extractor:

  1. Create src/extractors/mysite.ts extending BaseExtractor
  2. Implement canExtract(), extract(), and get siteName()
  3. Register in src/extractors/index.ts
export class MySiteExtractor extends BaseExtractor {
  get siteName(): string {
    return "My Site";
  }

  canExtract(): boolean {
    return !!this.querySelector(".my-content");
  }

  extract(): ExtractorResult {
    // Custom extraction logic
    return { content, textContent, metadata };
  }
}

Testing

Reader extracts content from live web pages, whose markup — and whose paywalls — change constantly. To keep that brittle logic honest, the repo ships an automated test suite:

npm test

It covers content extraction, HTML cleaning, and paywall detection, including the specific regressions these features have hit before. The committed fixtures in tests/fixtures/ are synthetic pages that reproduce real paywall structure without copying any publisher's content, so the suite runs on a fresh clone with no extra setup. If you change extraction, cleaning, or paywall detection, please add a case — see CONTRIBUTING.md.

Summary Configuration

The extension uses a modular configuration system located in src/config/:

AI Model Configuration (ai.ts)

Controls which AI model and creativity level is used for each summary style. This allows fine-tuning performance per summary type.

export const AI_SUMMARY_CONFIG: Record<SummaryStyle, AIStyleConfig> = {
  overview: { model: AI.Model["OpenAI_GPT-5_nano"], creativity: "low" },
  "opposite-sides": { model: AI.Model["OpenAI_GPT-5_nano"], creativity: "low" },
  // ...
};

Prompt Templates (prompts.ts)

Contains all summary prompt templates in one place for easy comparison and editing.

export const SUMMARY_PROMPTS: Record<SummaryStyle, PromptConfig> = {
  overview: {
    label: "Overview",
    buildPrompt: (context) => `${context}\n\nSummarize this article...`,
  },
  // ...
};

Each prompt config includes:

  • label - Human-readable name shown in the UI
  • buildPrompt - Function that generates the full prompt from article context

Summary Styles

Style Description
Overview One-liner summary + 3 key bullet points
At a Glance Summary + Key Takeaways in a concise format
Comprehensive Fact-filled bullet points from the author's POV
Opposing Sides Two contrasting viewpoints from the article
The 5 Ws Who, What, Where, When, Why breakdown
Explain Like I'm 5 Simplified explanation using simple language
People, Places & Things Key entities extracted with context

Summary Output Language

All summary styles can be generated in your preferred language. Set the Summary Output Language preference to choose from 21 supported languages including English (default), Spanish, French, German, Japanese, Chinese, and more. When a non-English language is selected, summaries will be generated in that language regardless of the article's original language.

Browser Extension Integration

Reader Mode integrates with the Raycast browser extension to handle blocked pages and access authenticated content.

Handling Blocked Pages

Some websites (like Politico, Bloomberg, etc.) use bot detection that prevents direct content fetching. When this happens, Reader Mode automatically offers a browser extension fallback:

How It Works:

  1. Detection - When a 403 "Access Denied" error occurs, Reader Mode checks if you have the Raycast browser extension installed
  2. Instructions - Shows a friendly message with clear steps to access the content
  3. Browser Fallback - You can open the page in your browser and fetch content via the extension

Usage:

  1. Press Enter to open the URL in your browser
  2. Wait for the page to fully load
  3. Press ⌘ + R to fetch the content via the Raycast browser extension
  4. The article loads normally with full content

Re-importing Member-Only Content

For paywalled or member-only articles (like Medium member stories), you can re-import content from an authenticated browser session:

When to Use:

  • Medium member-only articles
  • Paywalled content from news sites
  • Any article requiring authentication to view full content

How It Works:

  1. Open the article in Reader Mode (you'll see a truncated or blocked version)
  2. Press ⌘ + ⇧ + R to trigger "Import from Browser Tab"
  3. Reader Mode finds the matching browser tab using the article's canonical URL
  4. If the tab is inactive, you'll be prompted to focus it first
  5. Content is re-imported with your authenticated session, showing the full article

Requirements:

  • Raycast browser extension must be installed
  • The article must be open in a browser tab
  • You must be logged in to the site in your browser
  • The browser tab must be active (focused) when importing

Inspiration: Defuddle

This extension's content extraction architecture was inspired by Defuddle, a content extraction library by @kepano. We initially attempted to use Defuddle directly, but found it wasn't well-suited for Raycast's environment:

  • DOM Environment: Defuddle expects a browser DOM, while Raycast extensions run in Node.js with linkedom
  • Bundle Size: Defuddle's full feature set added unnecessary weight for our use case
  • Output Format: We needed tighter integration with our metadata extraction and markdown conversion pipeline

Instead, we adopted Defuddle's excellent patterns:

  • Site-specific extractors with a clean base class architecture
  • Schema.org JSON-LD parsing for rich metadata extraction
  • Fallback chains for metadata (Schema.org → Open Graph → Twitter Cards → meta tags)
  • Comprehensive cleanup selectors for removing ads, navigation, and other distractions

This hybrid approach gives us the best of both worlds: Defuddle's battle-tested extraction patterns with tight Raycast integration.

Known Issues

Bracket Rendering

Square brackets [text] that appear in article content (such as editorial insertions in quotes) are automatically converted to parentheses (text) to prevent Raycast's markdown renderer from interpreting them as LaTeX math notation. This is a workaround for a rendering limitation and means the displayed text may differ slightly from the original source material.

Image Rendering

Image alt text and title attributes are automatically stripped to ensure proper rendering in Raycast. Images are displayed as ![](url) without descriptive text. This prevents rendering issues where long alt text or title attributes (especially those containing quotes) can break the markdown image syntax.

Additionally, relative image URLs (e.g., /image.jpg) are automatically converted to absolute URLs using the page's base URL to ensure images load properly.

Paywall Detection: Externally-Hidden Barriers

Paywall detection runs on the fetched page HTML only — it has no network access while scoring, so it cannot read a page's external stylesheets. It accounts for barrier markup hidden by the hidden/aria-hidden attributes, inline styles, inert containers (<template>, etc.), and the page's own inline <style> rules. It cannot tell that a barrier element (e.g. class="article-gate") is hidden by a rule in a linked stylesheet.

So a readable article that also ships an inactive, externally-hidden paywall template can occasionally be misclassified as paywalled and sent through the Paywall Hopper bypass. The impact is bounded: retrieved content only replaces the original when it is at least 20% longer (see article-loader.ts), and an already-complete article is unlikely to gain that much from an archive — so the practical cost is a redundant bypass attempt, not swapped-out content. Fixing it properly requires evaluating linked CSS, which is out of scope for the detector's static, no-network design. This is a deliberate, accepted trade-off; see docs/known-issues.md for the full rationale.

References

About

Read the web distraction-free in Raycast

Topics

Resources

License

Contributing

Stars

1 star

Watchers

1 watching

Forks

Contributors