A real CLI rewritten to run client-side via the lesson runner. Same JSON parsing + URL handling as khn (https://github.com/t3m3d/khn); HTTP swapped from `exec("curl ...")` to `fetch(url)`, time from `exec("date +%s")` to `nowSec()`.
// Tutorial 34: Hacker News reader, live in your browser
// A real CLI rewritten to run client-side via the lesson runner. Same
// JSON parsing + URL handling as khn (https://github.com/t3m3d/khn);
// HTTP swapped from `exec("curl ...")` to `fetch(url)`, time from
// `exec("date +%s")` to `nowSec()`.
import "k:json_parse"
// Strip the scheme + path so we display just the host of an article URL.
func host(url) {
let s = url
if startsWith(s, "https://") { s = substring(s, 8, len(s)) }
if startsWith(s, "http://") { s = substring(s, 7, len(s)) }
let slash = indexOf(s, "/")
if slash < 0 { emit s }
emit substring(s, 0, slash)
}
// Relative-age formatter — Krypton handles the unix-time math itself.
func ago(unix) {
let diff = nowSec() - toInt(unix)
if diff < 60 { emit "" + diff + "s ago" }
if diff < 3600 { emit "" + (diff / 60) + "m ago" }
if diff < 86400 { emit "" + (diff / 3600) + "h ago" }
emit "" + (diff / 86400) + "d ago"
}
func padL(s, w) {
let out = ""
let pad = w - len(s)
while pad > 0 { out = out + " "; pad -= 1 }
emit out + s
}
just run {
let api = "https://hacker-news.firebaseio.com/v0"
let limit = 10
kp("Fetching HN top stories...")
// 1. Top story IDs come back as "[123, 456, 789, ...]".
let raw = fetch(api + "/topstories.json")
let cleaned = replace(replace(replace(raw, "[", ""), "]", ""), " ", "")
let idList = replace(cleaned, ",", "\n")
let total = lineCount(idList)
kp("")
kp("Hacker News — top " + limit + " (live, fetched in your browser)")
kp("")
let i = 0
let shown = 0
while shown < limit && i < total {
let id = trim(getLine(idList, i))
if len(id) > 0 {
let itemRaw = fetch(api + "/item/" + id + ".json")
if len(itemRaw) > 0 {
let jp = jpParse(itemRaw)
let title = jpGet(jp, "title")
let score = jpGet(jp, "score")
let by = jpGet(jp, "by")
let url = jpGet(jp, "url")
let time = jpGet(jp, "time")
let kids = "0"
if jpHas(jp, "descendants") {
kids = jpGet(jp, "descendants")
}
let h = ""
if len(url) > 0 { h = host(url) }
// Row 1: rank + score + title.
kp("#" + padL("" + (shown + 1), 2) + " " +
padL("" + score, 4) + " " + title)
// Row 2: meta + comments link.
let meta = ""
if len(h) > 0 { meta = h + " | " }
kp(" " + meta + by + " | " + ago(time) + " | " +
kids + " comments")
kp(" https://news.ycombinator.com/item?id=" + id)
kp("")
shown += 1
}
}
i += 1
}
kp("--- " + shown + " stories shown. Live data, hit Run again for the latest.")
}
Click ▶ Run to execute. Lessons using k:fs/k:http/match/struct need the real runtime.
Tip: this code box is editable — tweak it, then hit Run, or copy into a .k file and run locally.
Run it locally: kcc -r tutorial/34_browser_hn.k