Skip to content
Back to skills

2682 2026 02 24 Feat Hacker News Source Plan 1ed1de43

ASecurity

Add Hacker News as a source to the last30days skill, using the free Algolia HN Search API (`hn.algolia.com/api/v1`). HN provides high-signal content from a technical audience — stories with high point counts and active comment threads are strong indicators of what the developer community actually cares about. No API key required.

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 11, 2026
researchpythongogitapisecuritydocumentation

Works with

  • api

Security analysis

A100/100

Scanned October 11, 2026

npx -y skills add tools-only/X-Skills --skill 2682-2026-02-24-feat-hacker-news-source-plan_1ed1de43 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 2682 2026 02 24 Feat Hacker News Source Plan 1ed1de43?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 2682 2026 02 24 Feat Hacker News Source Plan 1ed1de43
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-2682-2026-02-24-feat-hacker-news-source-plan-1ed1d/badge)](https://www.skillsdirectory.com/skills/tools-only-2682-2026-02-24-feat-hacker-news-source-plan-1ed1d)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
---
title: "feat: Add Hacker News as a 5th research source"
type: feat
status: completed
date: 2026-02-24
---

# feat: Add Hacker News as a 5th Research Source

## Overview

Add Hacker News as a source to the last30days skill, using the free Algolia HN Search API (`hn.algolia.com/api/v1`). HN provides high-signal content from a technical audience — stories with high point counts and active comment threads are strong indicators of what the developer community actually cares about. No API key required.

## Problem Statement / Motivation

The skill currently covers Reddit, X, YouTube, and web. Hacker News is missing — and for technical topics, HN often surfaces discussions that don't appear on Reddit or X. HN's upvote system and comment culture produce high-quality signal: a 500-point story with 200 comments means the developer community is genuinely engaged. Community contributor @wkbaran proposed this in PR #26 alongside YouTube and Product Hunt. YouTube shipped in v2.1; now it's time for HN.

## Proposed Solution

Add `scripts/lib/hackernews.py` following the exact same pattern as `youtube_yt.py` (the simplest existing source — no API key, just HTTP calls). Use the Algolia HN Search API for discovery, then optionally fetch top comments from high-scoring stories for enrichment (like Reddit enrichment, but using the `/items/:id` endpoint instead of Reddit's JSON API).

### Two-phase approach (matches existing Reddit pattern):

1. **Phase 1 — Search**: Query Algolia for stories matching the topic within the date range. Get titles, URLs, points, comment counts.
2. **Phase 2 — Enrichment** (optional, top N stories): Fetch the `/items/:id` endpoint for the highest-scoring stories to get top-level comments. This gives "comment_insights" like Reddit enrichment does.

## Technical Approach

### Files to Create

#### `scripts/lib/hackernews.py`

The main source module. Pattern matches `youtube_yt.py` (simplest source).

```python
# scripts/lib/hackernews.py
"""Hacker News search via Algolia API (free, no auth required)."""

ALGOLIA_SEARCH_URL = "https://hn.algolia.com/api/v1/search"
ALGOLIA_SEARCH_BY_DATE_URL = "https://hn.algolia.com/api/v1/search_by_date"
ALGOLIA_ITEM_URL = "https://hn.algolia.com/api/v1/items"

DEPTH_CONFIG = {
    "quick": 15,
    "default": 30,
    "deep": 60,
}

ENRICH_LIMITS = {
    "quick": 3,
    "default": 5,
    "deep": 10,
}
```

Key functions:

- `search_hackernews(topic, from_date, to_date, depth="default") -> Dict[str, Any]`
  - Calls `hn.algolia.com/api/v1/search?query={topic}&tags=story&numericFilters=created_at_i>{from_ts},created_at_i<{to_ts}&hitsPerPage={count}`
  - Uses `http.get()` — stdlib only, matches existing pattern
  - Returns raw Algolia response

- `parse_hackernews_response(response: Dict) -> List[Dict]`
  - Extracts hits, maps to raw dicts with fields: `id` (prefix "HN"), `title`, `url`, `hn_url`, `author`, `date`, `engagement` (points, num_comments), `why_relevant`, `relevance`
  - `relevance` estimated from Algolia rank + engagement boost (same pattern as Bill's PR)

- `enrich_top_stories(items, depth="default") -> List[Dict]`
  - Fetches `/items/{objectID}` for top N stories (by points)
  - Extracts top-level comments (author, text, points)
  - Adds `top_comments` and `comment_insights` fields (same structure as Reddit enrichment)
  - Uses `ThreadPoolExecutor` for parallel fetching

- `_date_to_unix(date_str: str) -> int` — Helper, converts YYYY-MM-DD to Unix timestamp

#### `tests/test_hackernews.py`

Standard unittest pattern matching existing tests.

- Test `parse_hackernews_response` with sample Algolia response
- Test `_date_to_unix` conversion
- Test empty response handling
- Test enrichment parsing
- Test score integration with `score.py`

### Files to Modify

#### `scripts/lib/schema.py`

- [x] Add `HackerNewsItem` dataclass:
  ```python
  @dataclass
  class HackerNewsItem:
      id: str           # "HN1", "HN2", ...
      title: str
      url: str          # Original article URL
      hn_url: str       # news.ycombinator.com/item?id=...
      author: str       # HN username
      date: Optional[str]
      date_confidence: str  # Always "high" (Algolia provides exact timestamps)
      engagement: Optional[Engagement]  # points + num_comments
      top_comments: List[Comment]       # From enrichment
      comment_insights: List[str]       # From enrichment
      relevance: float
      why_relevant: str
      subs: SubScores
      score: int
  ```
- [x] Add `hackernews: List[HackerNewsItem] = field(default_factory=list)` to `Report`
- [x] Add `hackernews_error: Optional[str] = None` to `Report`
- [x] Update `Report.to_dict()` and `Report.from_dict()`

#### `scripts/lib/normalize.py`

- [x] Add `normalize_hackernews_items(items: List[Dict], from_date, to_date) -> List[schema.HackerNewsItem]`
  - Maps raw dicts to `HackerNewsItem` dataclass instances
  - Sets `date_confidence = "high"` (Algolia provides `created_at_i` exact timestamps)
  - Converts `engagement` dict to `schema.Engagement(score=points, num_comments=num_comments)`

#### `scripts/lib/score.py`

- [x] Add `compute_hackernews_engagement_raw(engagement) -> float`
  - Formula: `0.55 * log1p(points) + 0.45 * log1p(num_comments)`
  - Points are the primary signal on HN; comments indicate depth of discussion
- [x] Add `score_hackernews_items(items) -> List[schema.HackerNewsItem]`
  - Uses standard 45/25/30 weights (relevance/recency/engagement) — same as Reddit/X/YouTube
- [x] Update `sort_items()` to handle `HackerNewsItem`
  - Source priority: Reddit > X > **HN** > YouTube > WebSearch
  - HN slots between X and YouTube: higher signal than YouTube (curated upvotes vs raw views), but X has real-time pulse

#### `scripts/lib/dedupe.py`

- [x] Add `dedupe_hackernews(items, threshold=0.7) -> List[schema.HackerNewsItem]`
- [x] Update `get_item_text()` to handle `HackerNewsItem` (return `title`)
- [x] Consider cross-source dedup: HN stories often link to the same URLs that appear in web search results. Dedupe by URL match across `hackernews` and `websearch` items.

#### `scripts/lib/render.py`

- [x] Add HN section to `render_compact()`:
  ```
  ### Hacker News Stories

  **HN1** (score:85) hn/username (2026-02-15) [350pts, 127cmt]
    Story title here
    https://news.ycombinator.com/item?id=12345
    *Why relevant*
  ```
- [x] Add to `render_source_status()`:
  ```
  ✅ HN: {N} stories
  ```
- [x] Add to `render_full_report()` and `render_context_snippet()`
- [x] Update `_assess_data_freshness()` to include HN items

#### `scripts/last30days.py`

- [x] Import: `from lib import hackernews`
- [x] Add `_search_hackernews(topic, from_date, to_date, depth) -> (items, error)` wrapper function
  - Calls `hackernews.search_hackernews()`, then `hackernews.parse_hackernews_response()`
  - Returns `(items, None)` or `([], error_string)`
- [x] Add to `TIMEOUT_PROFILES`: `"hackernews_future": 60` (default), `30` (quick), `90` (deep)
- [x] Add HN to `ThreadPoolExecutor` in `run_research()`:
  ```python
  if do_hackernews:
      hn_future = executor.submit(_search_hackernews, topic, from_date, to_date, depth)
  ```
  - Increment `max_workers` by 1 when HN is enabled
- [x] Collect results: `hn_items, hn_error = hn_future.result(timeout=hn_timeout)`
- [x] Add HN enrichment phase (after Reddit enrichment, before Phase 2):
  ```python
  if hn_items:
      hn_items = hackernews.enrich_top_stories(hn_items, depth=depth)
  ```
- [x] Add to processing pipeline: normalize -> filter_by_date_range -> score -> sort -> dedupe
- [x] Assign to `report.hackernews` and `report.hackernews_error`
- [x] Update status UI: add `⏳ 🟡 HN Searching Hacker News...` and `✓ 🟡 HN Found {N} stories`

#### `scripts/lib/env.py`

- [x] HN is always available (no API key, no binary dependency)
- [x] Update `get_available_sources()` to include HN in the source list
- [x] Update `validate_sources()` to accept `hn` as a valid source name
- [x] Add `hn` to the `--search` flag documentation

#### `SKILL.md`

- [x] Update description: "Sources: Reddit, X, YouTube, **Hacker News**, and web"
- [x] Add HN to stats block template:
  ```
  ├─ 🟡 HN: {N} stories │ {N} points │ {N} comments
  ```
- [x] Update `metadata.clawdbot.tags` to include `hackernews`
- [x] Update Security section: add `hn.algolia.com` to endpoints list
- [x] Update citation priority to include HN: Reddit > X > YouTube > HN > Web

## Acceptance Criteria

- [x] `python3 scripts/last30days.py "AI coding agents" --emit=compact` shows HN section with stories, points, and comment counts
- [x] HN stories include `hn_url` linking to the HN discussion page (not just the article URL)
- [x] Top stories are enriched with top comments (like Reddit enrichment)
- [x] HN runs in parallel with Reddit/X/YouTube (no serial bottleneck)
- [x] Scoring uses standard 45/25/30 weights with engagement formula tuned for HN metrics
- [x] Stats block shows: `├─ 🟡 HN: {N} stories │ {N} points │ {N} comments`
- [x] `--search=hn` works to run HN only; `--search=reddit,hn` works for combos
- [x] `--quick`, `--deep` flags adjust HN result count (15/30/60)
- [x] All existing tests still pass
- [x] New `tests/test_hackernews.py` with tests for parse, normalize, score, enrichment
- [x] No API key required — works out of the box
- [x] Cross-source URL dedup: HN stories linking to same URL as web results get deduped
- [x] SKILL.md updated with HN in stats block, security section, and citation priority

## Dependencies & Risks

**Low risk:**
- Algolia HN API is free, public, no auth, well-established (used since 2014)
- No new dependencies — uses existing `http.py` (stdlib urllib)
- Pattern is identical to YouTube source (simplest existing source)

**Medium risk:**
- Algolia has no officially documented rate limit, but aggressive use could get throttled
  - Mitigation: Existing `http.py` exponential backoff handles 429s
  - Default depth only requests 30 items (1 API call for search + N for enrichment)
- Comment enrichment adds N API calls (one per story) which could slow down quick mode
  - Mitigation: Limit enrichment to top 3/5/10 stories by depth; use ThreadPoolExecutor

**Compatibility:**
- HN always available — doesn't break anything when other sources are missing
- Existing `--search` flag needs extension but is backward-compatible

## Sources & References

- PR #26 by @wkbaran: [HN implementation reference](https://github.com/mvanhorn/last30days-skill/pull/26)
- Algolia HN API docs: `https://hn.algolia.com/api`
- Existing patterns: `scripts/lib/youtube_yt.py` (simplest source), `scripts/lib/openai_reddit.py` (enrichment pattern)
- Scoring reference: `scripts/lib/score.py:compute_reddit_engagement_raw()`
- Schema reference: `scripts/lib/schema.py:RedditItem` (closest analog to HN)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.