Decentralized browser-based web crawler. Turn your browser into a voluntary crawl node that feeds the shared SIP-01 index on Nostr β the canonical Search Index Protocol v1.2. No backend. No tracking. No accounts required.
Every page you crawl becomes a kind 39697 web index observation β instantly searchable by 0xSearchstr, 0xPresearchstr, UNCAGED, and any future SIP-01 compatible client.
Live: https://crawlstr.shakespeare.wtf
You add a seed URL
β
βΌ
βββββββββββββββ
β Crawler β IndexedDB queue, persistent across sessions
β Engine β Battery/WiFi/bandwidth aware
ββββββββ¬βββββββ
β
βΌ
Fetch page (respects robots.txt, rate-limited per domain)
β
βΌ
Parse HTML (title, description, text, links, language)
β
βΌ
SHA-256 content hash (dedup across the network)
β
βΌ
Sign kind 39697 event with per-device indexer key
β
βΌ
Publish to Nostr relays (SIP-01)
β
βΌ
ββββββββββββββββββββββββββββββββββββββββ
β Shared SIP-01 Index β
β β
β 0xSearchstr reads it β
β 0xPresearchstr reads it β
β UNCAGED reads it β
β Your fork reads it β
β Any SIP-01 client reads it β
ββββββββββββββββββββββββββββββββββββββββ
Most "decentralized search" projects still run centralized crawlers. Crawlstr makes every browser a potential crawler β opt-in, transparent, resource-aware.
| Feature | Description |
|---|---|
| Opt-in only | Nothing runs without explicitly pressing "Start Crawling" |
| π² Random Scout | One button β picks a curated starting point you haven't scouted and goes |
| SIP-01 native | Same protocol as 0xSearchstr, 0xPresearchstr, UNCAGED β one shared index |
| Per-device identity | Anonymous indexer keypair, separate from your Nostr identity |
| No query leakage | Events contain page metadata only β never what anyone searched for |
| Resource aware | Battery, WiFi, bandwidth limits. Eco mode. Charging-only mode. |
| robots.txt | Respected by default (configurable) |
| Rate limited | 5β8 seconds between requests per domain |
| Persistent queue | IndexedDB-backed, survives browser restarts |
| Offline capable | Crawl queue persists; publishes when Nostr is reachable |
| Network heartbeat | Kind 16919 β visible on the SIP-01 dashboard while running |
| PWA | Installable, works on mobile and desktop |
Crawlstr is deliberately not a small copy of Indexstr. They're two classes of node:
THE INDEXSTR NETWORK
β
βββββββββββββββ΄ββββββββββββββ
β β
πͺΆ CRAWLSTR π INDEXSTR
Lightweight scout Heavyweight indexer
"I found something" "I operate indexing capacity"
β β
βββββββββββββββ¬ββββββββββββββ
βΌ
NOSTR / SIP-01
β
βββββββββββββββΌββββββββββββββ
βΌ βΌ βΌ
0xSearchstr UNCAGED Other engines
| πͺΆ Crawlstr (this) | π Indexstr | |
|---|---|---|
| Role | Human-directed & random discovery | Systematic distributed crawling |
| Seeds | You paste a URL, or Random Scout picks | Bundled curated SQLite collections |
| Queue | Small, session-scoped | Massive, sharded across 256 slots |
| Discovery | Links, feeds, sitemaps, canonical | Collection URLs at scale |
| Device | Any browser, incl. a phone on WiFi | Desktop/VPS-class contribution |
| Coupling | None. Both speak SIP-01 to Nostr. | None. Both speak SIP-01 to Nostr. |
Crawlstr finds. Indexstr maintains. Nostr distributes. Searchstr searches.
git clone https://github.com/NostrDanish/Crwalstr.git
cd Crwalstr
npm install
npm run devOpen the printed URL, add a seed URL, press Start Crawling.
The fastest way to contribute: press Explore a random corner of the web and Crawlstr shows you a starting point β its category and URL β before anything happens. Scout This starts the crawl; Another reshuffles. No account, no configuration, no URL needed.
Selection history stays in your browser (localStorage) and is never published β only the resulting page observations go to Nostr.
The corpus lives in src/data/seeds/*.txt β one category per plain-text file, so improving the dataset never means touching crawler logic:
src/data/seeds/
βββ nostr.txt β Nostr ecosystem
βββ bitcoin.txt β Bitcoin & open money
βββ dev.txt β Dev resources & awesome lists
βββ science.txt β Science & education
βββ books.txt β Books, archives & libraries
βββ music.txt β Music, art & culture
βββ games.txt β Games
βββ opendata.txt β Open data & internet infrastructure
βββ indie.txt β Indie blogs & the small web
Not plain Math.random() over a flat list. Each pick rolls a weighted strategy, tuned for long-tail coverage:
| Weight | Strategy | Picks from |
|---|---|---|
| 40% | Fresh | Seeds this device has never scouted |
| 25% | Rare | Seeds this device has scouted least often |
| 15% | Category | The category this device has explored least |
| 10% | Stale | Seeds not scouted for the longest time |
| 10% | Random | Pure randomness |
Previewing a seed (the "random corner" card) does not count as scouting it β a dismissed preview never penalizes the seed. The selection commits only when the crawl actually starts.
Enter any URL in the Seed URLs tab:
https://bitcoin.org
The crawler fetches the page, extracts content, hashes it, signs a SIP-01 observation, and publishes to Nostr. Then it follows links up to depth 3.
| Mode | Budget | Purpose |
|---|---|---|
| Quick Scan | ~5 pages | Fast inspection of one site |
| Site Scan | ~30 pages | A useful, complete crawl |
| Deep Scan | ~150 pages | Deeper crawl of a larger site |
| Volunteer | until stopped | Continuous contribution, every limit still applies |
When a mode's budget is spent, the crawler stops cleanly on its own. Random Explorer (continuous random scouting) is opt-in via startExplorer β never the default.
Crawlstr is a scout, so it prioritizes cheap, high-value discovery over bulk fetching:
- RSS/Atom feeds β one small XML file yields current content URLs with titles and dates; entries are indexed directly and queued for full fetches
- Sitemaps β
Sitemap:declarations in robots.txt, plus/sitemap.xml; sampled and bounded, never a firehose - Canonical URLs β observations are filed under the page's claimed canonical identity
- Link graph β same-domain links followed with per-domain rate limits; login/cart/calendar traps filtered out
| Setting | Default | Description |
|---|---|---|
| WiFi Only | Off | Only crawl on WiFi networks |
| Charging Only | Off | Only crawl while device is charging |
| Respect robots.txt | On | Follow website crawling policies |
| Eco Mode | On | Slower crawling, less resource usage |
| Follow RSS/Atom feeds | On | Index feed entries (cheap discovery) |
| Read sitemaps | On | Use sitemap.xml for discovery (sampled) |
Each browser gets its own anonymous indexer keypair (visible in the dashboard). This key signs all kind 39697 observations. It is:
- Pseudonymous β not linked to your personal Nostr identity
- Replaceable β regenerating creates a new indexer
- Local β the secret key never leaves your browser
- Exportable β for backup or migration
Crawlstr publishes SIP-01 (Search Index Protocol) events β the same protocol used by the entire Searchstr ecosystem.
{
"kind": 39697,
"pubkey": "<device indexer pubkey>",
"created_at": 1786250000,
"content": "{\"title\":\"Example Page\",\"description\":\"A page about...\"}",
"tags": [
["d", "widx:9f86d081884c7d659a2feaa0c55ad015"],
["u", "https://example.com/page"],
["l", "en"],
["x", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"],
["v", "1"],
["source", "crawlstr/1"],
["network", "clearnet"],
["type", "page"],
["alt", "Web index observation: Example Page"]
]
}| Tag | Meaning |
|---|---|
d |
"widx:" + sha256(normalized_url)[0:32] β URL identity, identical across all indexers |
u |
Canonical URL (normalized per SIP-01 Β§7) |
x |
Content hash: sha256(title + "\n" + description) |
v |
Schema version "1" |
l |
ISO 639-1 language code |
source |
"crawlstr/1" |
network |
Extension registry (Β§9.2) β always clearnet for a browser crawler |
type |
Extension registry β repository for GitHub/GitLab, else page |
alt |
Human-readable description (the alt convention, spec Β§12.3) |
Full schema documentation: NIP.md Β· Canonical spec: SIP-01 v1.1
Observations are published to:
SIP-01-aware index relays:
wss://relay-na1.metanomalist.com/wss://test-sip-relay.sip-01test.workers.dev/wss://sip-relay-2.sip-booster-relay.workers.dev/wss://sip-relay-3.uncaged-sip.workers.dev/wss://sip-relay-4.sip-relay-4.workers.dev/
Search relays (NIP-50):
wss://relay.nostr.band/wss://relay.ditto.pub/wss://search.nos.today/(read-only β "blocked: writes disabled")wss://relay.noswhere.com/
Write relays (for propagation):
wss://jskitty.cat/nostrwss://relay.primal.net/wss://relay.damus.io/wss://nostr.hifish.org/ws://acuy3mjnv26tkyaaucndlxmg2ocntz4rtebhavk57vgruozm42iaznqd.onion/(Tor only β reachable from Tor Browser; clearnet browsers skip it)
Each observation is pushed to every relay in the set via targeted per-relay connections.
Custom relays: add your own in Settings β Publish relays. They persist locally and merge into the publish set. The built-in set can't be removed.
Auto-discovery: Settings β Discover NIP-50 / SIP-01 relays queries relay-monitor announcements (NIP-66 kind 30166), filters to relays advertising NIP-50, then verifies each candidate with a live capability probe (NIP-11 β checking supported_nips for 50 and the uncaged_index block for SIP-01). Verified candidates appear with one-click add; nothing is added automatically. Discovery is a hint, never a requirement β the built-in set keeps the app fully functional without it (NIP-66's risk guidance).
Capability testing: every relay in the list has a test button that probes its NIP-11 document and shows SIP-01 / NIP-50 badges plus latency.
Crawlstr is one more independent indexer in the SIP-01 ecosystem:
Crawlstr (browser crawler)
β
βΌ kind 39697, signed by device indexer key
Nostr Relays
β
ββββ 0xSearchstr (search engine) reads it
ββββ 0xPresearchstr (search engine) reads it
ββββ UNCAGED (search engine template) reads it
ββββ Any SIP-01 client reads it
Multiple crawlers observing the same URL produce events with the same d tag and different pubkeys β search nodes group by d and count distinct authors ("7 independent indexers saw this page").
Crawlstr is honest about what a browser can and cannot do:
- CORS β A browser cannot read cross-origin responses unless the site sends CORS headers, and most don't. Crawlstr tries a direct fetch first, then falls back to a CORS proxy so real websites can actually be crawled. The trade-off is honest: when the proxy is used, the proxy operator sees which URL was fetched (never a search query, never a user identity). The dashboard shows the direct/proxy split per session.
- JavaScript rendering β Crawlstr parses static HTML. Single-page apps that require JavaScript rendering won't have their full content extracted.
- Background execution β Mobile browsers may throttle or kill background tabs. The crawler is most effective when the tab is active.
- Rate limits β Per-domain rate limiting is built-in (5β8 seconds between requests). This is respectful by design.
- Resource budgets are real β the bandwidth cap counts every byte (pages, robots.txt, feeds, sitemaps, proxy overhead), and the pages/hour cap is enforced on a sliding window. The page-size cap is clamped to the remaining hourly budget so a single page can't overshoot it.
- SSRF guard at the proxy boundary β before any URL is handed to fetch (direct or proxied), it's checked against localhost, RFC1918, link-local (incl. cloud metadata
169.254.169.254), CGNAT, and the odd IPv4 forms browsers accept (integer, hex, octal, short), plus IPv6 loopback/ULA/link-local. Direct-path redirects are re-checked against the same list. The remaining gap β a public URL that 302s into private space at the proxy β is the proxy operator's to close, and we say so.
For unrestricted crawling, run a desktop/CLI SIP-01 crawler alongside Crawlstr.
- React 19 + TypeScript + Vite
- TailwindCSS 4 + shadcn/ui
- Nostrify β Nostr relay pool
- nostr-tools β Event signing (
finalizeEvent) - idb β IndexedDB wrapper for the crawl queue
- TanStack Query β Data fetching + caching
- PWA β Service worker, manifest, installable
src/
βββ crawler/
β βββ engine.ts β Main crawler orchestrator (crawl loop, queue, scheduling)
β βββ queue.ts β IndexedDB queue (ready-job scheduling) + observed/fetched split
β βββ fetcher.ts β HTTP fetcher (CORS, timeout, SSRF-guarded, metered)
β βββ safety.ts β SSRF guard β refuses non-public targets at the proxy boundary
β βββ meter.ts β Sliding-window resource accounting (every byte, every page)
β βββ parser.ts β HTML parser (title, description, text, links, language)
β βββ hasher.ts β SHA-256 content hashing for local dedup
β βββ webIndex.ts β SIP-01: URL normalization, event build/parse (byte-compatible)
β βββ indexerIdentity.ts β Per-device anonymous indexer keypair
β βββ publisher.ts β Signs + publishes kind 39697 via finalizeEvent
β βββ relays.ts β Ecosystem relay pool configuration
β βββ robots.ts β robots.txt parser (policies + Sitemap: discovery)
β βββ feed.ts β RSS/Atom detection + parsing (cheap discovery)
β βββ sitemap.ts β XML sitemap parsing (urlset + sitemapindex, sampled)
β βββ seeds.ts β Random Scout selection engine (weighted strategies)
β βββ limits.ts β Per-domain rate limiting
β βββ types.ts β TypeScript interfaces (incl. crawl modes)
βββ data/
β βββ seeds/ β The seed corpus: one plain-text file per category
βββ components/
β βββ crawler/
β βββ CrawlerDashboard.tsx β Main UI (toggle, stats, seed, history, settings)
βββ hooks/
β βββ useCrawler.ts β React hook wiring engine to Nostr
βββ pages/
β βββ Index.tsx β Landing page + dashboard
βββ NIP.md β Protocol documentation
| Project | Role | URL |
|---|---|---|
| Crawlstr (this) | Lightweight scout β SIP-01 publisher | crawlstr.shakespeare.wtf |
| Indexstr | Heavyweight distributed indexer w/ collections | β |
| 0xSearchstr | Search engine β SIP-01 reader | 0xsearchstr.shakespeare.wtf |
| 0xPresearchstr | Community fork with keyword staking | presearchstr.shakespeare.wtf |
| UNCAGED-ENGINE | Minimal search engine template | uncaged.shakespeare.wtf |
- No login required to crawl. No account. No tracking.
- Crawl observations are signed by a per-device anonymous keypair, never your personal Nostr identity.
- Events contain page metadata only β never search queries, never browsing history.
- Your crawl history stays in your browser (IndexedDB). Clearing browser data removes it.
- Relay operators see the observation event and your IP address β that's how Nostr works. Key separation is guaranteed; network anonymity is not.
- Use a VPN or Tor β we recommend NymVPN.
Support us: https://nym.com/pricing?ref=aYPKAFmGpJi
MIT
Vibed with Shakespeare
