Open /pow/dergigi, /pow/fiatjaf.com, or /pow/npub…. Handles load
GitHub; domains and NIP-05 identifiers resolve to Nostr. Existing /pow?gh=dergigi
links and p=npub… or repeated repo=owner/repo parameters also work. The browser combines sources; the backend accepts exactly one source
per request. No person registry, identity mappings, or combined reports are stored.
Social previews use @vercel/og at /api/pow/og, with the same source parameters
and optional year. Person pages serve Open Graph and Twitter card metadata in
the initial HTML while keeping the existing React app. The card combines cached
activity with per-source counts, an avatar, and coverage markings. It never
starts activity backfills. Profile discovery uses the existing independent CDN
caches, has a 12-second budget, and shares the page's verification rules.
Rendered images are CDN-cached for one hour, or one minute when source months
are missing or unavailable. Social platforms may retain their own copies longer.
No new database tables, stored account associations, or Blob storage are needed.
Avatars use bounded HTTPS downloads with public DNS pinning; unavailable images
fall back to an initial. /pow previews always show the combined activity for
the URL's sources and year; temporary browser-only filters are not included.
/api/pow/source?kind=github&value=dergigi uses a Neon Postgres source cache.
Set server-only DATABASE_URL and GITHUB_TOKEN; run npm run db:migrate once
against that database. For local setup using pulled Vercel variables:
node --env-file=.env.local --import tsx scripts/migrate-pow.ts.
Use vercel dev for the frontend and API together.
p= and ngit= also accept NIP-05 addresses, such as dergigi.com,
fiatjaf.com, or sync@nostr.boutique. Bare domains resolve the _ name.
The browser resolves each address independently through /api/pow/resolve, then
loads activity under the resulting npub. Human-readable addresses stay in the
page URL. Successful public lookups are cached at the CDN for one hour and in
the browser HTTP cache for five minutes; no identity directory is stored in the
database. Lookups use HTTPS, reject redirects, and allow only public IP addresses. Source
labels and matching timeline authors use the NIP-05 identifier from the latest
observed signed profile only after its domain resolves back to the same npub.
Root identifiers display as bare domains. Failed or mismatched checks keep the
npub visible; filtering and activity cache keys continue to use the original key.
Account discovery automatically loads npubs in GitHub bios, website
fields, or social links. These are references, not ownership proofs. In the other
direction, NIP-39 github: claims are read from signed kind-10011 lists (with
legacy kind-0 fallback) and loaded only when a public, single-file gist has the
claimed GitHub owner and the exact NIP-39 verification text for that npub.
A root website or a njump.to/njump.me NIP-05 link in a GitHub profile can
also resolve through NIP-05. In the
reverse direction, a signed Nostr profile can suggest a GitHub handle through
its GitHub website link or the first label of its root NIP-05 domain. That
candidate is loaded only when its GitHub profile links back to the same npub
or qualifying NIP-05 domain; a matching name alone is insufficient.
Profile READMEs and unproven GitHub mentions in Nostr bios are not scanned.
The browser matches the evidence and adds discovered accounts to the URL without
a confirmation step, preserving filters and the eight-source limit. Public
profiles and gists are cached independently at the CDN for one hour. GitHub
profile documents, including social links, are also cached per handle in Postgres
for one hour, with up to 24 hours of stale reuse during outages. The browser
retries throttled discovery requests using the server’s retry delay. Discovery
never stores cross-account mappings.
An npub in p= loads both text notes and ngit activity. Use ngit=npub…
to load code activity alone. NIP-34 patches, PRs, PR updates, issues, NIP-22 code
comments, status messages, repository announcements and ref updates appear in an
amber ngit/GRASP heatmap. All events are signature-checked and attributed to their
signer. A published patch or ref update is counted as one event, not as a Git commit.
repo= also accepts nostr://npub/identifier, nostr://npub/relay/identifier,
NIP-19 repository naddr values, and GRASP HTTPS URLs ending in
/npub/identifier.git. These show repository context from all contributors.
Repository comments/statuses without repository address tags can be missing.
NIP-05 clone addresses and raw Git object/commit history are not supported yet.
The ngit collector discovers public relays from signed GRASP lists, outbox lists,
and repository announcements, with relay.ngit.dev, nos.lol, and relay.damus.io
as discovery relays. Relay discovery is cached per source for 24 hours. Monthly
pagination checkpoints use the existing queue and source cache. Public WSS relays
on port 443 are supported; resolved IPs are checked and pinned before connecting.
No new credentials or database migration are required.
Relay-backed heatmaps remain striped: relays can omit data, and replaceable repository state does not provide a complete history of pushes. Previously seen ngit events are retained when refreshing a month, even if a relay later drops them.
TODO: ngit/GRASP Git commit history
- Fetch Git history from public GRASP repositories in background workers, with resumable backfills for the selected year.
- Cache immutable commits indefinitely by repository and commit hash; fetch new history incrementally and avoid duplicate counts across mirrors.
- Distinguish commit authors, committers, and npub-signed ref publishers. Define evidence for attributing commits to an npub; never assume the person pushing a branch authored every commit. Keep uncertain attribution visible and preserve the rule against storing account associations or a people registry.
- Show commit activity separately from ref updates, with explicit date semantics and coverage indicators. Missing refs, shallow history, or unavailable objects must remain uncertain gaps. Git history alone cannot reconstruct push dates lost when relays replace older ref updates.
TODO: NIP-66 relay health reports
- Fetch and verify signed
kind:30166reports from multiple trusted monitors. Cache reports per relay and monitor, with timestamps and roughly hourly refreshes. Treat stale or missing reports as unknown. - Show reported latency and access requirements in the relay panel, with the monitor and report time, separately from our own cached fetch results.
- Evaluate report reliability before using it to prioritize discovery and fallback relays. Keep trying authors' advertised outbox relays; missing reports or a single monitor's claims must not prevent connections. Relay health must never imply complete activity history.
The browser defaults to 365 days and queues all requested months up front. A year selector
loads calendar years back to 2008, stored as year=2025 in the view URL. Each
platform has a separate heatmap, yearly total, and coverage indicators.
The current month stays fresh for 24 hours; finished historical months are cached
indefinitely. Incomplete months retry after one hour. Failed requests preserve
cached results and back off for two minutes, or until GitHub permits retrying.
After five consecutive collection errors, a job stops and can be requested again
after an hour. A database lease prevents duplicate
fetches. Each cache key identifies one source and month; associations remain in
the URL. Completed historical snapshots have no automatic expiry. A month fetched while
it was current is fetched through month-end once it closes. Edits, deletions, and
indexing changes after that require explicit cache invalidation.
GitHub collection paginates commit and issue/PR search results. Intervals with
more than 1,000 results are subdivided. Search and thread pagination progress is
saved between worker deliveries. It discovers additional threads with involves:
and reviewed-by: searches, then reads issue timelines, submitted reviews, and
inline review comments. Comment and review authors, or status-event actors,
determine attribution. Actions are dated when performed, not when the issue/PR
was created. Closing, reopening, merging, draft, and ready-for-review transitions
appear under “status changes”; comments and reviews have their own filter groups.
Discovery uses thread updates from the requested month onward, including later
updates, so old actions on recently updated threads are not excluded.
Thread pages are cached independently for 24 hours by repository, issue/PR,
endpoint, and page, and reused across people and months. GitHub monthly snapshots
use v4 keys; existing v3 activity remains visible as incomplete while the new
collector backfills. Older preview deployments cannot overwrite these expanded
snapshots. Nostr also uses v4 keys for outbox backfills; ngit keys remain unchanged.
No database migration is needed.
Cross-repository discovery is not exhaustive: searches can miss threads where
someone only changed a status, and deleted or inaccessible content is unavailable.
Person heatmaps therefore retain uncertain coverage even after all discovered
pages have been fetched. This uncertainty does not trigger perpetual historical
backfills: completed historical collection checkpoints remain cached indefinitely.
Extra GitHub repos include all contributors. Other git hosts are not supported.
Nostr discovers the author's latest signed NIP-65 (kind 10002) relay list and
reads kind-1 notes from its write relays (including entries marked for both read
and write). These are the relays where the author publishes; read-only entries
are for receiving mentions. Up to eight advertised write relays are queried,
plus relay.damus.io, nos.lol, and relay.primal.net as fallbacks.
Discovery queries those three plus purplepag.es, relay.nostr.com,
nostr.bitcoiner.social, nostr.mom, relay.snort.social, nos.relay,
nostr.inosta.cc, and nostr.wine. Discovery is cached per pubkey for 24 hours;
failed discovery is retried after an hour. A cached signed list survives discovery
outages, while a newer empty list clears its advertised relays. NIP-66 health
reports are not used yet.
Monthly pagination resumes through the queue, with bounded retries for failing relays. Previously observed posts are retained. Existing v3 months stay visible while requested history is supplemented using outbox relays. Completed historical months remain cached indefinitely; later relay-list changes do not automatically reopen them. Partial months retry after an hour. Only public WSS relays on port 443 are supported. Relay coverage can never prove inactivity. All Nostr days, person-level GitHub discovery, unfetched periods, and incomplete GitHub repository periods are striped in the heatmap. Plain empty repository cells mean no indexed activity in the fetched GitHub categories. Today remains uncertain until complete. Click a day to filter the timeline. The source form is hidden when URL parameters are present. Nostr links use njump.to. The relay icon beside copy shows cached Nostr/ngit fetch provenance: relay URL, last fetch time, result, and events returned by that fetch. The dots describe recorded fetches, not live connections. Older snapshots without provenance remain usable and are not refetched solely to populate this panel.
Vercel Queues runs separate GitHub and Nostr consumers, configured in vercel.json.
No extra queue credentials are needed on Vercel. GitHub has one worker per deployment;
Nostr has two. A shared database throttle spaces GitHub search requests and honors
rate-limit reset and Retry-After headers across deployments. Jobs contain one source
and one month. Leases deduplicate jobs across visitors, preview, and production.
The page polls cached results every ten seconds; queued jobs continue after it closes.
Queue messages expire after seven days; revisiting a source recovers expired jobs.
Production and previews share the cache, so schema changes must remain backwards
compatible. The old budget table is retained for older previews but these workers
no longer use it. Very large month responses are bounded at 3 MB and explicitly
marked incomplete.
Activity requests identify one source at a time and suppress Referer headers. Social image requests include the URL-selected sources; the renderer combines them in memory and caches the resulting image at the CDN without storing account associations in the database. Full page URLs can still appear in browser history and hosting access logs; do not add analytics that store query strings. Database credentials stay server-side.
Static activity dashboard for a set of GitHub, GitLab, Forgejo, and plain
git repos. Renders commits, PRs, issues, and releases as a
git log --oneline-style timeline.
Live at heartbeat.opensats.org
A build-time fetcher pulls activity data and writes public/data/events.json.
The browser never talks to GitHub or GitLab directly, so visitors don't burn
any rate-limit budget.
Today GitHub, public GitLab, Forgejo/Gitea instances (e.g. codeberg.org or git.rust-bitcoin.org), and plain git remotes (e.g. cgit hosts like git.zx2c4.com) are wired up. The plan is to also pull from nostr-native hosts like gitworkshop.dev.
Requires Node 22+.
npm install
export GITHUB_TOKEN=ghp_yourtoken # any PAT; only needed for GitHub repos
npm run fetch # writes public/data/events.json
npm run devPublic GitLab and Forgejo repos are fetched without auth.
Each repos*.yml file at the project root lists tracked repos; all matching
files are merged and deduplicated.
repos:
- owner/repo-1
- owner/repo-2
- provider: gitlab
repo: group/project
# host: gitlab.com
- provider: forgejo
repo: owner/repo
# host: codeberg.org
- provider: git
url: https://git.zx2c4.com/wireguard-toolsForgejo (and Gitea) instances are queried via their REST API; commits, PRs, issues, and releases all show up, same as GitHub.
Plain git repos are fetched with a shallow git clone, so only commits and
tags show up (there are no PRs or issues on a bare remote). Event links use
cgit-style URLs (<url>/commit/?id=<hash>).
Knobs (time window, page sizes) live at the top of
scripts/fetch.ts.
Built for Vercel. Set GITHUB_TOKEN as an env var when GitHub repos are
tracked; build:vercel runs npm run fetch && npm run build once per deployment via vercel.json. For periodic
refreshes, save a Vercel Deploy Hook URL as the VERCEL_DEPLOY_HOOK_URL repo
secret and the included refresh.yml
workflow pings it every 6 hours.
Use these paths on your Heartbeat deployment:
/pow/pow/l0rinc/pow/dtonon/pow/dergigi/pow/fiatjaf/pow/adi2011/pow/pablof7z/pow/lovvtide/pow/tnblumer/pow/D33r-Gee/pow/davidrodt/pow/slater-jay/pow/fiatjaf.com/pow/dergigi.com/pow/suhailsaqan/pow/greenart7c3/pow/johngribbin/pow/DanConwayDev/pow/LibertyFarmer/pow/danconwaydev.com/pow/dergigi?year=2025/pow/fiatjaf.com?gh=fiatjaf/pow/Kukks?p=kukks@kukks.org/pow?repo=OpenSats%2Fwebsite/pow/jackjack?p=jack@primal.net/pow/npub1gcxzte5zlkncx26j68ez60fzkvtkm9e0vrwdcvsjakxf9mu9qewqlfnj5z/pow/npub1l2vyh47mk2p0qlsku7hg0vn29faehy9hy34ygaclpn66ukqp3afqutajft/pow/callebtc?p=npub12rv5lskctqxxs2c8rf2zlzc7xx3qpvzs3w4etgemauy9thegr43sf485vg/pow/npub1n3w4uts2r4srq3adqu9trp953dflcnw7ppn72tad4htkpsckwcmqjef6um?gh=BoltTouring/pow/DanConwayDev?p=npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr