Resources
6Install
npx skillscat add imoonkey/openweb/src-sites-hackernews Install via the SkillsCat registry.
Hacker News
Overview
Tech news aggregator by Y Combinator. Reads via Algolia Search API and Firebase API (node-direct). Writes via browser page context.
Workflows
Browse and read a story
getTopStories→ pick story → noteobjectIDgetStoryDetail(id)→ title, url, points, author, nested comment tree
Upvote a story
getTopStories→ pick story →objectIDupvoteStory(id=objectID)→{ok, id}(requires login)unvoteStory(id=${prev.upvoteStory.id})→{ok, id}— reverses the upvote (only valid while upvoted)
Comment on a story
getStoryDetail(id)→item.idaddComment(parent=item.id, text)→{ok, parent, id}—idis the new comment's id (requires login)deleteComment(id=${prev.addComment.id})→{ok, id}— must be within HN's ~2-hour delete window
Reply to a comment
getStoryDetail(id)→children[]→ pick comment →comment.idaddComment(parent=comment.id, text)→{ok, parent, id}— posts reply (requires login)
Explore a user
getUserProfile(id)→ karma, created, aboutgetUserSubmissions(id)→ stories they postedgetUserComments(id)→ their comment history
Find stories from a domain
getStoriesByDomain(query)→ all stories linking to that domain
Read latest activity
getNewComments→ newest comments across all stories
Operations
| Operation | Intent | Key Input | Key Output | Transport |
|---|---|---|---|---|
| getTopStories | browse top stories | — | objectID, title, url, author, points, num_comments | L1 node (Algolia) |
| getNewestStories | browse newest | — | same as above | L1 node (Algolia) |
| getBestStories | all-time highest-voted | — | same as above | L1 node (Algolia) |
| getAskStories | recent Ask HN | — | same as above | L1 node (Algolia) |
| getShowStories | recent Show HN | — | same as above | L1 node (Algolia) |
| getJobPostings | browse jobs | — | objectID, title, url, created_at | L1 node (Algolia) |
| getFrontPageStories | time-based front page | — | same as feeds | L1 node (Algolia) |
| getStoryDetail | story + comment tree | id (item ID) | id, title, url, points, children[] | L1 node (Algolia) |
| getUserProfile | user profile | id (username) | id, karma, created, about | L1 node (Firebase) |
| getNewComments | newest comments | — | objectID, author, comment_text, story_title | L1 node (Algolia) |
| getStoryComments | comment thread | id, limit? | storyId, commentCount, comments[] | adapter (Algolia) |
| getStoriesByDomain | recent domain stories | query (domain) | objectID, title, url, author, points | L1 node (Algolia) |
| getUserSubmissions | user's stories | id (username) | objectID, title, url, author, points | adapter (Algolia) |
| getUserComments | user's comments | id (username) | objectID, author, comment_text | adapter (Algolia) |
| upvoteStory | upvote item | id <- feeds/getStoryDetail | ok, id | adapter (page) |
| unvoteStory | reverse upvote | id <- upvoteStory | ok, id | adapter (page) |
| addComment | post comment | parent <- getStoryDetail, text | ok, parent, id | adapter (page) |
| deleteComment | delete own comment | id <- addComment | ok, id | adapter (page); ~2-hour window |
Raw Algolia wire shape
Read operations hit https://hn.algolia.com/api/v1/search over the node transport. The raw Algolia response is:
{
"hits": [ /* ... */ ],
"nbHits": 1234,
"hitsPerPage": 20,
"page": 0,
"nbPages": 50,
"processingTimeMS": 3,
"query": "",
"params": "tags=story"
}The spec declares unwrap: hits, so adapters/agents receive just the hits array — the envelope (nbHits, page, etc.) is stripped by the runtime.
Each hit carries Algolia-indexed fields:
objectID— story/comment id as a string (cast if you need a number)title,url,author,points,num_commentsstory_text,comment_text— HTML strings (see note below)created_at(ISO),created_at_i(unix seconds)_tags— e.g.["story", "author_pg", "story_12345"]story_id,parent_id— for comments
Templated reads
Some reads template the id param into an Algolia filter/tag expression:
getStoryComments→numericFilters=story_id={id}getUserSubmissions→tags=story,author_{id}getUserComments→tags=comment,author_{id}
Because id is used as a template source, the runtime does not emit it as a bare query key — only the interpolated filter/tag appears on the wire.
HTML in text fields
comment_text and story_text are HTML fragments (typically wrapped in <p> or <pre>). When rendering, strip those tags (and decode entities) rather than displaying raw markup.
Quick Start
# Browse top stories (node-direct, no browser needed)
openweb hackernews exec getTopStories '{}'
# Get story detail with full comment tree
openweb hackernews exec getStoryDetail '{"id": 42407357}'
# Get comments for a story (with limit)
openweb hackernews exec getStoryComments '{"id": 42407357, "limit": 10}'
# Look up a user (Firebase API)
openweb hackernews exec getUserProfile '{"id": "pg"}'
# User's submitted stories
openweb hackernews exec getUserSubmissions '{"id": "pg"}'
# Stories from a domain
openweb hackernews exec getStoriesByDomain '{"query": "github.com"}'
# Latest comments site-wide
openweb hackernews exec getNewComments '{}'
# Upvote a story (requires browser + login)
openweb hackernews exec upvoteStory '{"id": 42407357}'
# Reverse the upvote (only valid while currently upvoted)
openweb hackernews exec unvoteStory '{"id": 42407357}'
# Comment on a story (requires browser + login)
openweb hackernews exec addComment '{"parent": 42407357, "text": "Great article!"}'
# Delete your own comment (HN ~2-hour delete window)
openweb hackernews exec deleteComment '{"id": 47830121}'