Repository files navigation

Criblist

the sf + nyc hunt, minus the hunting.

Live demo · CIMIT license

Criblist turns live San Francisco and New York City rental inventory into a small, preference-matched deck of apartments to review, swipe, and shortlist. It is a runnable product and a reference implementation of the Context.dev Web Extraction API.

Important

Rental availability, prices, and details can change at any time. Always verify a listing with its publisher before applying or sending money.

What it does

  • Searches fourteen live rental marketplaces and property managers across two cities
  • Filters by budget, bedrooms, bathrooms, neighborhood, laundry, pets, dishwasher, and size
  • Normalizes different source formats into one validated Apartment Card
  • Ranks and diversifies each Apartment Deck so one provider cannot dominate it
  • Stores preferences, deck progress, and saved homes in the browser only
  • Optionally keeps a persistent Turso inventory for faster repeat searches

How Context.dev is used

Criblist has four acquisition paths that share the same validation, ranking, and deduplication pipeline:

  1. The HTML API renders Craigslist search and detail pages.
  2. The Markdown API turns filtered StreetEasy result pages into a compact, parseable inventory stream.
  3. The Extract API turns property-manager inventory pages into typed listing candidates.
  4. Direct adapters read structured public inventory endpoints and page markup.

The Brand API enriches the provider list with current brand identities.

All Context.dev calls happen on the server. The API key is never sent to the browser.

Quick start

Requirements

Install and run

git clone https://github.com/context-dot-dev/crib-shortlist.git
cd crib-shortlist
npm ci
cp .env.example .env.local

Add your Context.dev key to .env.local:

CONTEXT_DEV_API_KEY=your_key_here

Then start the development server:

npm run dev

Open http://localhost:3000. A Turso database is not required for local development; without one, searches acquire listings live.

Optional persistent inventory

Turso lets Criblist serve fresh inventory immediately and refresh it outside a user request. Create a Turso database, add both credentials to .env.local, and warm it once:

TURSO_DATABASE_URL=libsql://your-database.turso.ioTURSO_AUTH_TOKEN=your_token_here
npm run cache:warm

Pass --city=sf or --city=nyc to refresh one city while developing. Add --source=<source-id> or --bedrooms=<studio|1|2|3+> to narrow a debugging run:

npm run cache:warm -- --city=nyc
npm run cache:warm -- --city=nyc --source=streeteasy --bedrooms=2

The tables and indexes are created automatically. To refresh continuously during local development, run:

npm run cache:watch -- --interval-minutes=30

The interval must be at least five minutes. Warming queries live sites and can consume Context.dev credits.

Environment variables

VariableRequiredPurpose
CONTEXT_DEV_API_KEYFor searchContext.dev HTML, Extract, image, and Brand API access
TURSO_DATABASE_URLFor persistent inventoryTurso/libSQL database URL; must be set with TURSO_AUTH_TOKEN
TURSO_AUTH_TOKENFor persistent inventoryAuthenticates Turso reads and writes; must be set with TURSO_DATABASE_URL
CRON_SECRETFor scheduled refreshesProtects GET /api/cron/refresh-listings with a bearer token
NEXT_PUBLIC_SITE_URLNoCanonical origin used for social metadata outside Vercel
CRIBLIST_BASE_URLNoTarget URL for npm run stress:search; defaults to http://localhost:3000

Never commit .env, .env.local, API keys, or database credentials. The provided .env.example contains names and comments only.

Commands

CommandWhat it does
npm run devStarts the Next.js development server
npm run checkRuns linting, TypeScript checks, and the unit test suite
npm run lintRuns ESLint across the repository
npm run typecheckChecks TypeScript without emitting files
npm testRuns deterministic tests for contracts, parsing, ranking, and caching
npm run buildCreates a production Next.js build
npm startServes an existing production build
npm run cache:warmRefreshes every provider and bedroom segment into Turso
npm run cache:watch -- --interval-minutes=30Repeats the inventory refresh locally
npm run stress:searchExercises live search lanes against a running app

stress:search, cache:warm, and cache:watch make real upstream requests. They are intentionally excluded from npm run check and CI.

Architecture

app/
├── _components/criblist/ product UI and browser-local state
├── _components/ui/ reusable visual primitives
├── _lib/ browser utilities
├── api/apartment-search/ validated search HTTP route
├── api/cron/refresh-listings/ protected inventory refresh route
└── api/provider-brands/ provider brand enrichment route
server/
├── brand/ Context.dev provider enrichment
├── cache/ Turso inventory and refresh orchestration
└── search/ source adapters, normalization, and ranking
shared/
├── cities.ts city metadata and neighborhood catalogs
├── providers.ts city-aware provider catalog and search-lane mapping
└── search-contract.ts shared browser/server schemas
scripts/ live stress and inventory utilities
tests/ deterministic Node test suite

The browser sends one validated Preferences object to POST /api/apartment-search?source=all. The server first checks Turso when it is configured. If every requested inventory segment is fresh, it builds a deck from the stored cards. Otherwise, source adapters run concurrently, failures are isolated per source, and successful cards pass through the same quality, deduplication, preference, and provider-diversity rules. Best-effort results are written back to Turso.

The API also accepts fast, independent, craigslist, and extract source lanes for diagnostics and targeted testing. A completed Apartment Deck contains at most eight cards.

See CONTEXT.md for the domain language used throughout the codebase.

Live sources

San Francisco

  • Craigslist San Francisco
  • Brick + Timber
  • RentSFNow
  • Mosser Living
  • J. Wavro Associates
  • Rentals Inc.
  • Rentals in SF
  • Landmark Real Estate
  • ReLISTO

New York City

  • StreetEasy
  • Nooklyn
  • The Brodsky Organization
  • Stonehenge NYC
  • Craigslist New York City

Adapters begin at each publisher's current-availability page. Upstream HTML, APIs, and access policies can change without notice, so source fixes should include a focused parser regression test.

Troubleshooting

  • Search says a Context.dev key is required: confirm CONTEXT_DEV_API_KEY is set in .env.local, then restart npm run dev.
  • A cache command asks for Turso credentials:TURSO_DATABASE_URL and TURSO_AUTH_TOKEN must be configured together. They are optional for normal local searches.
  • One provider returns no cards: source failures are isolated and live sites change independently. Try broader Preferences, then check the provider's current-availability page and the corresponding adapter test.
  • npm ci reports an unsupported engine: switch to a supported LTS release; nvm use reads the repository's .nvmrc.

Deployment

Any Node.js host that supports Next.js can run Criblist with npm run build and npm start. For Vercel:

  1. Import the repository.
  2. Set CONTEXT_DEV_API_KEY.
  3. To enable persistent inventory, also set TURSO_DATABASE_URL, TURSO_AUTH_TOKEN, and a long random CRON_SECRET.
  4. Set NEXT_PUBLIC_SITE_URL when the production URL cannot be inferred from Vercel's environment.

vercel.json schedules the protected inventory route once a day at 08:00 UTC. Other schedulers must call the same route with Authorization: Bearer <CRON_SECRET>.

Contributing and security

Contributions are welcome. Read CONTRIBUTING.md before opening a pull request and follow the Code of Conduct.

Please report vulnerabilities privately as described in SECURITY.md, not in a public issue.

Data and trademark notice

Criblist is an independent discovery interface, not a rental broker or listing provider. Provider names and trademarks belong to their respective owners. Listing text, photos, and other content fetched at runtime remain subject to the publisher's terms and rights. Review those terms before operating a public deployment or adapting an adapter for another site.

License

Criblist is available under the MIT License.

About

A swipeable shortlist of live San Francisco apartment listings, powered by Context.dev.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

12 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

Criblist

the sf + nyc hunt, minus the hunting.

Live demo · CIMIT license

Criblist turns live San Francisco and New York City rental inventory into a small, preference-matched deck of apartments to review, swipe, and shortlist. It is a runnable product and a reference implementation of the Context.dev Web Extraction API.

Important

Rental availability, prices, and details can change at any time. Always verify a listing with its publisher before applying or sending money.

What it does

  • Searches fourteen live rental marketplaces and property managers across two cities
  • Filters by budget, bedrooms, bathrooms, neighborhood, laundry, pets, dishwasher, and size
  • Normalizes different source formats into one validated Apartment Card
  • Ranks and diversifies each Apartment Deck so one provider cannot dominate it
  • Stores preferences, deck progress, and saved homes in the browser only
  • Optionally keeps a persistent Turso inventory for faster repeat searches

How Context.dev is used

Criblist has four acquisition paths that share the same validation, ranking, and deduplication pipeline:

  1. The HTML API renders Craigslist search and detail pages.
  2. The Markdown API turns filtered StreetEasy result pages into a compact, parseable inventory stream.
  3. The Extract API turns property-manager inventory pages into typed listing candidates.
  4. Direct adapters read structured public inventory endpoints and page markup.

The Brand API enriches the provider list with current brand identities.

All Context.dev calls happen on the server. The API key is never sent to the browser.

Quick start

Requirements

Install and run

git clone https://github.com/context-dot-dev/crib-shortlist.git
cd crib-shortlist
npm ci
cp .env.example .env.local

Add your Context.dev key to .env.local:

CONTEXT_DEV_API_KEY=your_key_here

Then start the development server:

npm run dev

Open http://localhost:3000. A Turso database is not required for local development; without one, searches acquire listings live.

Optional persistent inventory

Turso lets Criblist serve fresh inventory immediately and refresh it outside a user request. Create a Turso database, add both credentials to .env.local, and warm it once:

TURSO_DATABASE_URL=libsql://your-database.turso.ioTURSO_AUTH_TOKEN=your_token_here
npm run cache:warm

Pass --city=sf or --city=nyc to refresh one city while developing. Add --source=<source-id> or --bedrooms=<studio|1|2|3+> to narrow a debugging run:

npm run cache:warm -- --city=nyc
npm run cache:warm -- --city=nyc --source=streeteasy --bedrooms=2

The tables and indexes are created automatically. To refresh continuously during local development, run:

npm run cache:watch -- --interval-minutes=30

The interval must be at least five minutes. Warming queries live sites and can consume Context.dev credits.

Environment variables

VariableRequiredPurpose
CONTEXT_DEV_API_KEYFor searchContext.dev HTML, Extract, image, and Brand API access
TURSO_DATABASE_URLFor persistent inventoryTurso/libSQL database URL; must be set with TURSO_AUTH_TOKEN
TURSO_AUTH_TOKENFor persistent inventoryAuthenticates Turso reads and writes; must be set with TURSO_DATABASE_URL
CRON_SECRETFor scheduled refreshesProtects GET /api/cron/refresh-listings with a bearer token
NEXT_PUBLIC_SITE_URLNoCanonical origin used for social metadata outside Vercel
CRIBLIST_BASE_URLNoTarget URL for npm run stress:search; defaults to http://localhost:3000

Never commit .env, .env.local, API keys, or database credentials. The provided .env.example contains names and comments only.

Commands

CommandWhat it does
npm run devStarts the Next.js development server
npm run checkRuns linting, TypeScript checks, and the unit test suite
npm run lintRuns ESLint across the repository
npm run typecheckChecks TypeScript without emitting files
npm testRuns deterministic tests for contracts, parsing, ranking, and caching
npm run buildCreates a production Next.js build
npm startServes an existing production build
npm run cache:warmRefreshes every provider and bedroom segment into Turso
npm run cache:watch -- --interval-minutes=30Repeats the inventory refresh locally
npm run stress:searchExercises live search lanes against a running app

stress:search, cache:warm, and cache:watch make real upstream requests. They are intentionally excluded from npm run check and CI.

Architecture

app/
├── _components/criblist/ product UI and browser-local state
├── _components/ui/ reusable visual primitives
├── _lib/ browser utilities
├── api/apartment-search/ validated search HTTP route
├── api/cron/refresh-listings/ protected inventory refresh route
└── api/provider-brands/ provider brand enrichment route
server/
├── brand/ Context.dev provider enrichment
├── cache/ Turso inventory and refresh orchestration
└── search/ source adapters, normalization, and ranking
shared/
├── cities.ts city metadata and neighborhood catalogs
├── providers.ts city-aware provider catalog and search-lane mapping
└── search-contract.ts shared browser/server schemas
scripts/ live stress and inventory utilities
tests/ deterministic Node test suite

The browser sends one validated Preferences object to POST /api/apartment-search?source=all. The server first checks Turso when it is configured. If every requested inventory segment is fresh, it builds a deck from the stored cards. Otherwise, source adapters run concurrently, failures are isolated per source, and successful cards pass through the same quality, deduplication, preference, and provider-diversity rules. Best-effort results are written back to Turso.

The API also accepts fast, independent, craigslist, and extract source lanes for diagnostics and targeted testing. A completed Apartment Deck contains at most eight cards.

See CONTEXT.md for the domain language used throughout the codebase.

Live sources

San Francisco

  • Craigslist San Francisco
  • Brick + Timber
  • RentSFNow
  • Mosser Living
  • J. Wavro Associates
  • Rentals Inc.
  • Rentals in SF
  • Landmark Real Estate
  • ReLISTO

New York City

  • StreetEasy
  • Nooklyn
  • The Brodsky Organization
  • Stonehenge NYC
  • Craigslist New York City

Adapters begin at each publisher's current-availability page. Upstream HTML, APIs, and access policies can change without notice, so source fixes should include a focused parser regression test.

Troubleshooting

  • Search says a Context.dev key is required: confirm CONTEXT_DEV_API_KEY is set in .env.local, then restart npm run dev.
  • A cache command asks for Turso credentials:TURSO_DATABASE_URL and TURSO_AUTH_TOKEN must be configured together. They are optional for normal local searches.
  • One provider returns no cards: source failures are isolated and live sites change independently. Try broader Preferences, then check the provider's current-availability page and the corresponding adapter test.
  • npm ci reports an unsupported engine: switch to a supported LTS release; nvm use reads the repository's .nvmrc.

Deployment

Any Node.js host that supports Next.js can run Criblist with npm run build and npm start. For Vercel:

  1. Import the repository.
  2. Set CONTEXT_DEV_API_KEY.
  3. To enable persistent inventory, also set TURSO_DATABASE_URL, TURSO_AUTH_TOKEN, and a long random CRON_SECRET.
  4. Set NEXT_PUBLIC_SITE_URL when the production URL cannot be inferred from Vercel's environment.

vercel.json schedules the protected inventory route once a day at 08:00 UTC. Other schedulers must call the same route with Authorization: Bearer <CRON_SECRET>.

Contributing and security

Contributions are welcome. Read CONTRIBUTING.md before opening a pull request and follow the Code of Conduct.

Please report vulnerabilities privately as described in SECURITY.md, not in a public issue.

Data and trademark notice

Criblist is an independent discovery interface, not a rental broker or listing provider. Provider names and trademarks belong to their respective owners. Listing text, photos, and other content fetched at runtime remain subject to the publisher's terms and rights. Review those terms before operating a public deployment or adapting an adapter for another site.

License

Criblist is available under the MIT License.

About

A swipeable shortlist of live San Francisco apartment listings, powered by Context.dev.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

12 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Criblist

the sf + nyc hunt, minus the hunting.

Live demo · CIMIT license

Criblist turns live San Francisco and New York City rental inventory into a small, preference-matched deck of apartments to review, swipe, and shortlist. It is a runnable product and a reference implementation of the Context.dev Web Extraction API.

Important

Rental availability, prices, and details can change at any time. Always verify a listing with its publisher before applying or sending money.

What it does

  • Searches fourteen live rental marketplaces and property managers across two cities
  • Filters by budget, bedrooms, bathrooms, neighborhood, laundry, pets, dishwasher, and size
  • Normalizes different source formats into one validated Apartment Card
  • Ranks and diversifies each Apartment Deck so one provider cannot dominate it
  • Stores preferences, deck progress, and saved homes in the browser only
  • Optionally keeps a persistent Turso inventory for faster repeat searches

How Context.dev is used

Criblist has four acquisition paths that share the same validation, ranking, and deduplication pipeline:

  1. The HTML API renders Craigslist search and detail pages.
  2. The Markdown API turns filtered StreetEasy result pages into a compact, parseable inventory stream.
  3. The Extract API turns property-manager inventory pages into typed listing candidates.
  4. Direct adapters read structured public inventory endpoints and page markup.

The Brand API enriches the provider list with current brand identities.

All Context.dev calls happen on the server. The API key is never sent to the browser.

Quick start

Requirements

Install and run

git clone https://github.com/context-dot-dev/crib-shortlist.git
cd crib-shortlist
npm ci
cp .env.example .env.local

Add your Context.dev key to .env.local:

CONTEXT_DEV_API_KEY=your_key_here

Then start the development server:

npm run dev

Open http://localhost:3000. A Turso database is not required for local development; without one, searches acquire listings live.

Optional persistent inventory

Turso lets Criblist serve fresh inventory immediately and refresh it outside a user request. Create a Turso database, add both credentials to .env.local, and warm it once:

TURSO_DATABASE_URL=libsql://your-database.turso.ioTURSO_AUTH_TOKEN=your_token_here
npm run cache:warm

Pass --city=sf or --city=nyc to refresh one city while developing. Add --source=<source-id> or --bedrooms=<studio|1|2|3+> to narrow a debugging run:

npm run cache:warm -- --city=nyc
npm run cache:warm -- --city=nyc --source=streeteasy --bedrooms=2

The tables and indexes are created automatically. To refresh continuously during local development, run:

npm run cache:watch -- --interval-minutes=30

The interval must be at least five minutes. Warming queries live sites and can consume Context.dev credits.

Environment variables

VariableRequiredPurpose
CONTEXT_DEV_API_KEYFor searchContext.dev HTML, Extract, image, and Brand API access
TURSO_DATABASE_URLFor persistent inventoryTurso/libSQL database URL; must be set with TURSO_AUTH_TOKEN
TURSO_AUTH_TOKENFor persistent inventoryAuthenticates Turso reads and writes; must be set with TURSO_DATABASE_URL
CRON_SECRETFor scheduled refreshesProtects GET /api/cron/refresh-listings with a bearer token
NEXT_PUBLIC_SITE_URLNoCanonical origin used for social metadata outside Vercel
CRIBLIST_BASE_URLNoTarget URL for npm run stress:search; defaults to http://localhost:3000

Never commit .env, .env.local, API keys, or database credentials. The provided .env.example contains names and comments only.

Commands

CommandWhat it does
npm run devStarts the Next.js development server
npm run checkRuns linting, TypeScript checks, and the unit test suite
npm run lintRuns ESLint across the repository
npm run typecheckChecks TypeScript without emitting files
npm testRuns deterministic tests for contracts, parsing, ranking, and caching
npm run buildCreates a production Next.js build
npm startServes an existing production build
npm run cache:warmRefreshes every provider and bedroom segment into Turso
npm run cache:watch -- --interval-minutes=30Repeats the inventory refresh locally
npm run stress:searchExercises live search lanes against a running app

stress:search, cache:warm, and cache:watch make real upstream requests. They are intentionally excluded from npm run check and CI.

Architecture

app/
├── _components/criblist/ product UI and browser-local state
├── _components/ui/ reusable visual primitives
├── _lib/ browser utilities
├── api/apartment-search/ validated search HTTP route
├── api/cron/refresh-listings/ protected inventory refresh route
└── api/provider-brands/ provider brand enrichment route
server/
├── brand/ Context.dev provider enrichment
├── cache/ Turso inventory and refresh orchestration
└── search/ source adapters, normalization, and ranking
shared/
├── cities.ts city metadata and neighborhood catalogs
├── providers.ts city-aware provider catalog and search-lane mapping
└── search-contract.ts shared browser/server schemas
scripts/ live stress and inventory utilities
tests/ deterministic Node test suite

The browser sends one validated Preferences object to POST /api/apartment-search?source=all. The server first checks Turso when it is configured. If every requested inventory segment is fresh, it builds a deck from the stored cards. Otherwise, source adapters run concurrently, failures are isolated per source, and successful cards pass through the same quality, deduplication, preference, and provider-diversity rules. Best-effort results are written back to Turso.

The API also accepts fast, independent, craigslist, and extract source lanes for diagnostics and targeted testing. A completed Apartment Deck contains at most eight cards.

See CONTEXT.md for the domain language used throughout the codebase.

Live sources

San Francisco

  • Craigslist San Francisco
  • Brick + Timber
  • RentSFNow
  • Mosser Living
  • J. Wavro Associates
  • Rentals Inc.
  • Rentals in SF
  • Landmark Real Estate
  • ReLISTO

New York City

  • StreetEasy
  • Nooklyn
  • The Brodsky Organization
  • Stonehenge NYC
  • Craigslist New York City

Adapters begin at each publisher's current-availability page. Upstream HTML, APIs, and access policies can change without notice, so source fixes should include a focused parser regression test.

Troubleshooting

  • Search says a Context.dev key is required: confirm CONTEXT_DEV_API_KEY is set in .env.local, then restart npm run dev.
  • A cache command asks for Turso credentials:TURSO_DATABASE_URL and TURSO_AUTH_TOKEN must be configured together. They are optional for normal local searches.
  • One provider returns no cards: source failures are isolated and live sites change independently. Try broader Preferences, then check the provider's current-availability page and the corresponding adapter test.
  • npm ci reports an unsupported engine: switch to a supported LTS release; nvm use reads the repository's .nvmrc.

Deployment

Any Node.js host that supports Next.js can run Criblist with npm run build and npm start. For Vercel:

  1. Import the repository.
  2. Set CONTEXT_DEV_API_KEY.
  3. To enable persistent inventory, also set TURSO_DATABASE_URL, TURSO_AUTH_TOKEN, and a long random CRON_SECRET.
  4. Set NEXT_PUBLIC_SITE_URL when the production URL cannot be inferred from Vercel's environment.

vercel.json schedules the protected inventory route once a day at 08:00 UTC. Other schedulers must call the same route with Authorization: Bearer <CRON_SECRET>.

Contributing and security

Contributions are welcome. Read CONTRIBUTING.md before opening a pull request and follow the Code of Conduct.

Please report vulnerabilities privately as described in SECURITY.md, not in a public issue.

Data and trademark notice

Criblist is an independent discovery interface, not a rental broker or listing provider. Provider names and trademarks belong to their respective owners. Listing text, photos, and other content fetched at runtime remain subject to the publisher's terms and rights. Review those terms before operating a public deployment or adapting an adapter for another site.

License

Criblist is available under the MIT License.

About

A swipeable shortlist of live San Francisco apartment listings, powered by Context.dev.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

12 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Criblist

the sf + nyc hunt, minus the hunting.

Live demo · CIMIT license

Criblist turns live San Francisco and New York City rental inventory into a small, preference-matched deck of apartments to review, swipe, and shortlist. It is a runnable product and a reference implementation of the Context.dev Web Extraction API.

Important

Rental availability, prices, and details can change at any time. Always verify a listing with its publisher before applying or sending money.

What it does

  • Searches fourteen live rental marketplaces and property managers across two cities
  • Filters by budget, bedrooms, bathrooms, neighborhood, laundry, pets, dishwasher, and size
  • Normalizes different source formats into one validated Apartment Card
  • Ranks and diversifies each Apartment Deck so one provider cannot dominate it
  • Stores preferences, deck progress, and saved homes in the browser only
  • Optionally keeps a persistent Turso inventory for faster repeat searches

How Context.dev is used

Criblist has four acquisition paths that share the same validation, ranking, and deduplication pipeline:

  1. The HTML API renders Craigslist search and detail pages.
  2. The Markdown API turns filtered StreetEasy result pages into a compact, parseable inventory stream.
  3. The Extract API turns property-manager inventory pages into typed listing candidates.
  4. Direct adapters read structured public inventory endpoints and page markup.

The Brand API enriches the provider list with current brand identities.

All Context.dev calls happen on the server. The API key is never sent to the browser.

Quick start

Requirements

Install and run

git clone https://github.com/context-dot-dev/crib-shortlist.git
cd crib-shortlist
npm ci
cp .env.example .env.local

Add your Context.dev key to .env.local:

CONTEXT_DEV_API_KEY=your_key_here

Then start the development server:

npm run dev

Open http://localhost:3000. A Turso database is not required for local development; without one, searches acquire listings live.

Optional persistent inventory

Turso lets Criblist serve fresh inventory immediately and refresh it outside a user request. Create a Turso database, add both credentials to .env.local, and warm it once:

TURSO_DATABASE_URL=libsql://your-database.turso.ioTURSO_AUTH_TOKEN=your_token_here
npm run cache:warm

Pass --city=sf or --city=nyc to refresh one city while developing. Add --source=<source-id> or --bedrooms=<studio|1|2|3+> to narrow a debugging run:

npm run cache:warm -- --city=nyc
npm run cache:warm -- --city=nyc --source=streeteasy --bedrooms=2

The tables and indexes are created automatically. To refresh continuously during local development, run:

npm run cache:watch -- --interval-minutes=30

The interval must be at least five minutes. Warming queries live sites and can consume Context.dev credits.

Environment variables

VariableRequiredPurpose
CONTEXT_DEV_API_KEYFor searchContext.dev HTML, Extract, image, and Brand API access
TURSO_DATABASE_URLFor persistent inventoryTurso/libSQL database URL; must be set with TURSO_AUTH_TOKEN
TURSO_AUTH_TOKENFor persistent inventoryAuthenticates Turso reads and writes; must be set with TURSO_DATABASE_URL
CRON_SECRETFor scheduled refreshesProtects GET /api/cron/refresh-listings with a bearer token
NEXT_PUBLIC_SITE_URLNoCanonical origin used for social metadata outside Vercel
CRIBLIST_BASE_URLNoTarget URL for npm run stress:search; defaults to http://localhost:3000

Never commit .env, .env.local, API keys, or database credentials. The provided .env.example contains names and comments only.

Commands

CommandWhat it does
npm run devStarts the Next.js development server
npm run checkRuns linting, TypeScript checks, and the unit test suite
npm run lintRuns ESLint across the repository
npm run typecheckChecks TypeScript without emitting files
npm testRuns deterministic tests for contracts, parsing, ranking, and caching
npm run buildCreates a production Next.js build
npm startServes an existing production build
npm run cache:warmRefreshes every provider and bedroom segment into Turso
npm run cache:watch -- --interval-minutes=30Repeats the inventory refresh locally
npm run stress:searchExercises live search lanes against a running app

stress:search, cache:warm, and cache:watch make real upstream requests. They are intentionally excluded from npm run check and CI.

Architecture

app/
├── _components/criblist/ product UI and browser-local state
├── _components/ui/ reusable visual primitives
├── _lib/ browser utilities
├── api/apartment-search/ validated search HTTP route
├── api/cron/refresh-listings/ protected inventory refresh route
└── api/provider-brands/ provider brand enrichment route
server/
├── brand/ Context.dev provider enrichment
├── cache/ Turso inventory and refresh orchestration
└── search/ source adapters, normalization, and ranking
shared/
├── cities.ts city metadata and neighborhood catalogs
├── providers.ts city-aware provider catalog and search-lane mapping
└── search-contract.ts shared browser/server schemas
scripts/ live stress and inventory utilities
tests/ deterministic Node test suite

The browser sends one validated Preferences object to POST /api/apartment-search?source=all. The server first checks Turso when it is configured. If every requested inventory segment is fresh, it builds a deck from the stored cards. Otherwise, source adapters run concurrently, failures are isolated per source, and successful cards pass through the same quality, deduplication, preference, and provider-diversity rules. Best-effort results are written back to Turso.

The API also accepts fast, independent, craigslist, and extract source lanes for diagnostics and targeted testing. A completed Apartment Deck contains at most eight cards.

See CONTEXT.md for the domain language used throughout the codebase.

Live sources

San Francisco

  • Craigslist San Francisco
  • Brick + Timber
  • RentSFNow
  • Mosser Living
  • J. Wavro Associates
  • Rentals Inc.
  • Rentals in SF
  • Landmark Real Estate
  • ReLISTO

New York City

  • StreetEasy
  • Nooklyn
  • The Brodsky Organization
  • Stonehenge NYC
  • Craigslist New York City

Adapters begin at each publisher's current-availability page. Upstream HTML, APIs, and access policies can change without notice, so source fixes should include a focused parser regression test.

Troubleshooting

  • Search says a Context.dev key is required: confirm CONTEXT_DEV_API_KEY is set in .env.local, then restart npm run dev.
  • A cache command asks for Turso credentials:TURSO_DATABASE_URL and TURSO_AUTH_TOKEN must be configured together. They are optional for normal local searches.
  • One provider returns no cards: source failures are isolated and live sites change independently. Try broader Preferences, then check the provider's current-availability page and the corresponding adapter test.
  • npm ci reports an unsupported engine: switch to a supported LTS release; nvm use reads the repository's .nvmrc.

Deployment

Any Node.js host that supports Next.js can run Criblist with npm run build and npm start. For Vercel:

  1. Import the repository.
  2. Set CONTEXT_DEV_API_KEY.
  3. To enable persistent inventory, also set TURSO_DATABASE_URL, TURSO_AUTH_TOKEN, and a long random CRON_SECRET.
  4. Set NEXT_PUBLIC_SITE_URL when the production URL cannot be inferred from Vercel's environment.

vercel.json schedules the protected inventory route once a day at 08:00 UTC. Other schedulers must call the same route with Authorization: Bearer <CRON_SECRET>.

Contributing and security

Contributions are welcome. Read CONTRIBUTING.md before opening a pull request and follow the Code of Conduct.

Please report vulnerabilities privately as described in SECURITY.md, not in a public issue.

Data and trademark notice

Criblist is an independent discovery interface, not a rental broker or listing provider. Provider names and trademarks belong to their respective owners. Listing text, photos, and other content fetched at runtime remain subject to the publisher's terms and rights. Review those terms before operating a public deployment or adapting an adapter for another site.

License

Criblist is available under the MIT License.

About

A swipeable shortlist of live San Francisco apartment listings, powered by Context.dev.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

12 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

Criblist

the sf + nyc hunt, minus the hunting.

Live demo · CIMIT license

Criblist turns live San Francisco and New York City rental inventory into a small, preference-matched deck of apartments to review, swipe, and shortlist. It is a runnable product and a reference implementation of the Context.dev Web Extraction API.

Important

Rental availability, prices, and details can change at any time. Always verify a listing with its publisher before applying or sending money.

What it does

  • Searches fourteen live rental marketplaces and property managers across two cities
  • Filters by budget, bedrooms, bathrooms, neighborhood, laundry, pets, dishwasher, and size
  • Normalizes different source formats into one validated Apartment Card
  • Ranks and diversifies each Apartment Deck so one provider cannot dominate it
  • Stores preferences, deck progress, and saved homes in the browser only
  • Optionally keeps a persistent Turso inventory for faster repeat searches

How Context.dev is used

Criblist has four acquisition paths that share the same validation, ranking, and deduplication pipeline:

  1. The HTML API renders Craigslist search and detail pages.
  2. The Markdown API turns filtered StreetEasy result pages into a compact, parseable inventory stream.
  3. The Extract API turns property-manager inventory pages into typed listing candidates.
  4. Direct adapters read structured public inventory endpoints and page markup.

The Brand API enriches the provider list with current brand identities.

All Context.dev calls happen on the server. The API key is never sent to the browser.

Quick start

Requirements

Install and run

git clone https://github.com/context-dot-dev/crib-shortlist.git
cd crib-shortlist
npm ci
cp .env.example .env.local

Add your Context.dev key to .env.local:

CONTEXT_DEV_API_KEY=your_key_here

Then start the development server:

npm run dev

Open http://localhost:3000. A Turso database is not required for local development; without one, searches acquire listings live.

Optional persistent inventory

Turso lets Criblist serve fresh inventory immediately and refresh it outside a user request. Create a Turso database, add both credentials to .env.local, and warm it once:

TURSO_DATABASE_URL=libsql://your-database.turso.ioTURSO_AUTH_TOKEN=your_token_here
npm run cache:warm

Pass --city=sf or --city=nyc to refresh one city while developing. Add --source=<source-id> or --bedrooms=<studio|1|2|3+> to narrow a debugging run:

npm run cache:warm -- --city=nyc
npm run cache:warm -- --city=nyc --source=streeteasy --bedrooms=2

The tables and indexes are created automatically. To refresh continuously during local development, run:

npm run cache:watch -- --interval-minutes=30

The interval must be at least five minutes. Warming queries live sites and can consume Context.dev credits.

Environment variables

VariableRequiredPurpose
CONTEXT_DEV_API_KEYFor searchContext.dev HTML, Extract, image, and Brand API access
TURSO_DATABASE_URLFor persistent inventoryTurso/libSQL database URL; must be set with TURSO_AUTH_TOKEN
TURSO_AUTH_TOKENFor persistent inventoryAuthenticates Turso reads and writes; must be set with TURSO_DATABASE_URL
CRON_SECRETFor scheduled refreshesProtects GET /api/cron/refresh-listings with a bearer token
NEXT_PUBLIC_SITE_URLNoCanonical origin used for social metadata outside Vercel
CRIBLIST_BASE_URLNoTarget URL for npm run stress:search; defaults to http://localhost:3000

Never commit .env, .env.local, API keys, or database credentials. The provided .env.example contains names and comments only.

Commands

CommandWhat it does
npm run devStarts the Next.js development server
npm run checkRuns linting, TypeScript checks, and the unit test suite
npm run lintRuns ESLint across the repository
npm run typecheckChecks TypeScript without emitting files
npm testRuns deterministic tests for contracts, parsing, ranking, and caching
npm run buildCreates a production Next.js build
npm startServes an existing production build
npm run cache:warmRefreshes every provider and bedroom segment into Turso
npm run cache:watch -- --interval-minutes=30Repeats the inventory refresh locally
npm run stress:searchExercises live search lanes against a running app

stress:search, cache:warm, and cache:watch make real upstream requests. They are intentionally excluded from npm run check and CI.

Architecture

app/
├── _components/criblist/ product UI and browser-local state
├── _components/ui/ reusable visual primitives
├── _lib/ browser utilities
├── api/apartment-search/ validated search HTTP route
├── api/cron/refresh-listings/ protected inventory refresh route
└── api/provider-brands/ provider brand enrichment route
server/
├── brand/ Context.dev provider enrichment
├── cache/ Turso inventory and refresh orchestration
└── search/ source adapters, normalization, and ranking
shared/
├── cities.ts city metadata and neighborhood catalogs
├── providers.ts city-aware provider catalog and search-lane mapping
└── search-contract.ts shared browser/server schemas
scripts/ live stress and inventory utilities
tests/ deterministic Node test suite

The browser sends one validated Preferences object to POST /api/apartment-search?source=all. The server first checks Turso when it is configured. If every requested inventory segment is fresh, it builds a deck from the stored cards. Otherwise, source adapters run concurrently, failures are isolated per source, and successful cards pass through the same quality, deduplication, preference, and provider-diversity rules. Best-effort results are written back to Turso.

The API also accepts fast, independent, craigslist, and extract source lanes for diagnostics and targeted testing. A completed Apartment Deck contains at most eight cards.

See CONTEXT.md for the domain language used throughout the codebase.

Live sources

San Francisco

  • Craigslist San Francisco
  • Brick + Timber
  • RentSFNow
  • Mosser Living
  • J. Wavro Associates
  • Rentals Inc.
  • Rentals in SF
  • Landmark Real Estate
  • ReLISTO

New York City

  • StreetEasy
  • Nooklyn
  • The Brodsky Organization
  • Stonehenge NYC
  • Craigslist New York City

Adapters begin at each publisher's current-availability page. Upstream HTML, APIs, and access policies can change without notice, so source fixes should include a focused parser regression test.

Troubleshooting

  • Search says a Context.dev key is required: confirm CONTEXT_DEV_API_KEY is set in .env.local, then restart npm run dev.
  • A cache command asks for Turso credentials:TURSO_DATABASE_URL and TURSO_AUTH_TOKEN must be configured together. They are optional for normal local searches.
  • One provider returns no cards: source failures are isolated and live sites change independently. Try broader Preferences, then check the provider's current-availability page and the corresponding adapter test.
  • npm ci reports an unsupported engine: switch to a supported LTS release; nvm use reads the repository's .nvmrc.

Deployment

Any Node.js host that supports Next.js can run Criblist with npm run build and npm start. For Vercel:

  1. Import the repository.
  2. Set CONTEXT_DEV_API_KEY.
  3. To enable persistent inventory, also set TURSO_DATABASE_URL, TURSO_AUTH_TOKEN, and a long random CRON_SECRET.
  4. Set NEXT_PUBLIC_SITE_URL when the production URL cannot be inferred from Vercel's environment.

vercel.json schedules the protected inventory route once a day at 08:00 UTC. Other schedulers must call the same route with Authorization: Bearer <CRON_SECRET>.

Contributing and security

Contributions are welcome. Read CONTRIBUTING.md before opening a pull request and follow the Code of Conduct.

Please report vulnerabilities privately as described in SECURITY.md, not in a public issue.

Data and trademark notice

Criblist is an independent discovery interface, not a rental broker or listing provider. Provider names and trademarks belong to their respective owners. Listing text, photos, and other content fetched at runtime remain subject to the publisher's terms and rights. Review those terms before operating a public deployment or adapting an adapter for another site.

License

Criblist is available under the MIT License.

About

A swipeable shortlist of live San Francisco apartment listings, powered by Context.dev.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

12 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Criblist

the sf + nyc hunt, minus the hunting.

Live demo · CIMIT license

Criblist turns live San Francisco and New York City rental inventory into a small, preference-matched deck of apartments to review, swipe, and shortlist. It is a runnable product and a reference implementation of the Context.dev Web Extraction API.

Important

Rental availability, prices, and details can change at any time. Always verify a listing with its publisher before applying or sending money.

What it does

  • Searches fourteen live rental marketplaces and property managers across two cities
  • Filters by budget, bedrooms, bathrooms, neighborhood, laundry, pets, dishwasher, and size
  • Normalizes different source formats into one validated Apartment Card
  • Ranks and diversifies each Apartment Deck so one provider cannot dominate it
  • Stores preferences, deck progress, and saved homes in the browser only
  • Optionally keeps a persistent Turso inventory for faster repeat searches

How Context.dev is used

Criblist has four acquisition paths that share the same validation, ranking, and deduplication pipeline:

  1. The HTML API renders Craigslist search and detail pages.
  2. The Markdown API turns filtered StreetEasy result pages into a compact, parseable inventory stream.
  3. The Extract API turns property-manager inventory pages into typed listing candidates.
  4. Direct adapters read structured public inventory endpoints and page markup.

The Brand API enriches the provider list with current brand identities.

All Context.dev calls happen on the server. The API key is never sent to the browser.

Quick start

Requirements

Install and run

git clone https://github.com/context-dot-dev/crib-shortlist.git
cd crib-shortlist
npm ci
cp .env.example .env.local

Add your Context.dev key to .env.local:

CONTEXT_DEV_API_KEY=your_key_here

Then start the development server:

npm run dev

Open http://localhost:3000. A Turso database is not required for local development; without one, searches acquire listings live.

Optional persistent inventory

Turso lets Criblist serve fresh inventory immediately and refresh it outside a user request. Create a Turso database, add both credentials to .env.local, and warm it once:

TURSO_DATABASE_URL=libsql://your-database.turso.ioTURSO_AUTH_TOKEN=your_token_here
npm run cache:warm

Pass --city=sf or --city=nyc to refresh one city while developing. Add --source=<source-id> or --bedrooms=<studio|1|2|3+> to narrow a debugging run:

npm run cache:warm -- --city=nyc
npm run cache:warm -- --city=nyc --source=streeteasy --bedrooms=2

The tables and indexes are created automatically. To refresh continuously during local development, run:

npm run cache:watch -- --interval-minutes=30

The interval must be at least five minutes. Warming queries live sites and can consume Context.dev credits.

Environment variables

VariableRequiredPurpose
CONTEXT_DEV_API_KEYFor searchContext.dev HTML, Extract, image, and Brand API access
TURSO_DATABASE_URLFor persistent inventoryTurso/libSQL database URL; must be set with TURSO_AUTH_TOKEN
TURSO_AUTH_TOKENFor persistent inventoryAuthenticates Turso reads and writes; must be set with TURSO_DATABASE_URL
CRON_SECRETFor scheduled refreshesProtects GET /api/cron/refresh-listings with a bearer token
NEXT_PUBLIC_SITE_URLNoCanonical origin used for social metadata outside Vercel
CRIBLIST_BASE_URLNoTarget URL for npm run stress:search; defaults to http://localhost:3000

Never commit .env, .env.local, API keys, or database credentials. The provided .env.example contains names and comments only.

Commands

CommandWhat it does
npm run devStarts the Next.js development server
npm run checkRuns linting, TypeScript checks, and the unit test suite
npm run lintRuns ESLint across the repository
npm run typecheckChecks TypeScript without emitting files
npm testRuns deterministic tests for contracts, parsing, ranking, and caching
npm run buildCreates a production Next.js build
npm startServes an existing production build
npm run cache:warmRefreshes every provider and bedroom segment into Turso
npm run cache:watch -- --interval-minutes=30Repeats the inventory refresh locally
npm run stress:searchExercises live search lanes against a running app

stress:search, cache:warm, and cache:watch make real upstream requests. They are intentionally excluded from npm run check and CI.

Architecture

app/
├── _components/criblist/ product UI and browser-local state
├── _components/ui/ reusable visual primitives
├── _lib/ browser utilities
├── api/apartment-search/ validated search HTTP route
├── api/cron/refresh-listings/ protected inventory refresh route
└── api/provider-brands/ provider brand enrichment route
server/
├── brand/ Context.dev provider enrichment
├── cache/ Turso inventory and refresh orchestration
└── search/ source adapters, normalization, and ranking
shared/
├── cities.ts city metadata and neighborhood catalogs
├── providers.ts city-aware provider catalog and search-lane mapping
└── search-contract.ts shared browser/server schemas
scripts/ live stress and inventory utilities
tests/ deterministic Node test suite

The browser sends one validated Preferences object to POST /api/apartment-search?source=all. The server first checks Turso when it is configured. If every requested inventory segment is fresh, it builds a deck from the stored cards. Otherwise, source adapters run concurrently, failures are isolated per source, and successful cards pass through the same quality, deduplication, preference, and provider-diversity rules. Best-effort results are written back to Turso.

The API also accepts fast, independent, craigslist, and extract source lanes for diagnostics and targeted testing. A completed Apartment Deck contains at most eight cards.

See CONTEXT.md for the domain language used throughout the codebase.

Live sources

San Francisco

  • Craigslist San Francisco
  • Brick + Timber
  • RentSFNow
  • Mosser Living
  • J. Wavro Associates
  • Rentals Inc.
  • Rentals in SF
  • Landmark Real Estate
  • ReLISTO

New York City

  • StreetEasy
  • Nooklyn
  • The Brodsky Organization
  • Stonehenge NYC
  • Craigslist New York City

Adapters begin at each publisher's current-availability page. Upstream HTML, APIs, and access policies can change without notice, so source fixes should include a focused parser regression test.

Troubleshooting

  • Search says a Context.dev key is required: confirm CONTEXT_DEV_API_KEY is set in .env.local, then restart npm run dev.
  • A cache command asks for Turso credentials:TURSO_DATABASE_URL and TURSO_AUTH_TOKEN must be configured together. They are optional for normal local searches.
  • One provider returns no cards: source failures are isolated and live sites change independently. Try broader Preferences, then check the provider's current-availability page and the corresponding adapter test.
  • npm ci reports an unsupported engine: switch to a supported LTS release; nvm use reads the repository's .nvmrc.

Deployment

Any Node.js host that supports Next.js can run Criblist with npm run build and npm start. For Vercel:

  1. Import the repository.
  2. Set CONTEXT_DEV_API_KEY.
  3. To enable persistent inventory, also set TURSO_DATABASE_URL, TURSO_AUTH_TOKEN, and a long random CRON_SECRET.
  4. Set NEXT_PUBLIC_SITE_URL when the production URL cannot be inferred from Vercel's environment.

vercel.json schedules the protected inventory route once a day at 08:00 UTC. Other schedulers must call the same route with Authorization: Bearer <CRON_SECRET>.

Contributing and security

Contributions are welcome. Read CONTRIBUTING.md before opening a pull request and follow the Code of Conduct.

Please report vulnerabilities privately as described in SECURITY.md, not in a public issue.

Data and trademark notice

Criblist is an independent discovery interface, not a rental broker or listing provider. Provider names and trademarks belong to their respective owners. Listing text, photos, and other content fetched at runtime remain subject to the publisher's terms and rights. Review those terms before operating a public deployment or adapting an adapter for another site.

License

Criblist is available under the MIT License.

About

A swipeable shortlist of live San Francisco apartment listings, powered by Context.dev.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

12 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

Criblist

the sf + nyc hunt, minus the hunting.

Live demo · CIMIT license

Criblist turns live San Francisco and New York City rental inventory into a small, preference-matched deck of apartments to review, swipe, and shortlist. It is a runnable product and a reference implementation of the Context.dev Web Extraction API.

Important

Rental availability, prices, and details can change at any time. Always verify a listing with its publisher before applying or sending money.

What it does

  • Searches fourteen live rental marketplaces and property managers across two cities
  • Filters by budget, bedrooms, bathrooms, neighborhood, laundry, pets, dishwasher, and size
  • Normalizes different source formats into one validated Apartment Card
  • Ranks and diversifies each Apartment Deck so one provider cannot dominate it
  • Stores preferences, deck progress, and saved homes in the browser only
  • Optionally keeps a persistent Turso inventory for faster repeat searches

How Context.dev is used

Criblist has four acquisition paths that share the same validation, ranking, and deduplication pipeline:

  1. The HTML API renders Craigslist search and detail pages.
  2. The Markdown API turns filtered StreetEasy result pages into a compact, parseable inventory stream.
  3. The Extract API turns property-manager inventory pages into typed listing candidates.
  4. Direct adapters read structured public inventory endpoints and page markup.

The Brand API enriches the provider list with current brand identities.

All Context.dev calls happen on the server. The API key is never sent to the browser.

Quick start

Requirements

Install and run

git clone https://github.com/context-dot-dev/crib-shortlist.git
cd crib-shortlist
npm ci
cp .env.example .env.local

Add your Context.dev key to .env.local:

CONTEXT_DEV_API_KEY=your_key_here

Then start the development server:

npm run dev

Open http://localhost:3000. A Turso database is not required for local development; without one, searches acquire listings live.

Optional persistent inventory

Turso lets Criblist serve fresh inventory immediately and refresh it outside a user request. Create a Turso database, add both credentials to .env.local, and warm it once:

TURSO_DATABASE_URL=libsql://your-database.turso.ioTURSO_AUTH_TOKEN=your_token_here
npm run cache:warm

Pass --city=sf or --city=nyc to refresh one city while developing. Add --source=<source-id> or --bedrooms=<studio|1|2|3+> to narrow a debugging run:

npm run cache:warm -- --city=nyc
npm run cache:warm -- --city=nyc --source=streeteasy --bedrooms=2

The tables and indexes are created automatically. To refresh continuously during local development, run:

npm run cache:watch -- --interval-minutes=30

The interval must be at least five minutes. Warming queries live sites and can consume Context.dev credits.

Environment variables

VariableRequiredPurpose
CONTEXT_DEV_API_KEYFor searchContext.dev HTML, Extract, image, and Brand API access
TURSO_DATABASE_URLFor persistent inventoryTurso/libSQL database URL; must be set with TURSO_AUTH_TOKEN
TURSO_AUTH_TOKENFor persistent inventoryAuthenticates Turso reads and writes; must be set with TURSO_DATABASE_URL
CRON_SECRETFor scheduled refreshesProtects GET /api/cron/refresh-listings with a bearer token
NEXT_PUBLIC_SITE_URLNoCanonical origin used for social metadata outside Vercel
CRIBLIST_BASE_URLNoTarget URL for npm run stress:search; defaults to http://localhost:3000

Never commit .env, .env.local, API keys, or database credentials. The provided .env.example contains names and comments only.

Commands

CommandWhat it does
npm run devStarts the Next.js development server
npm run checkRuns linting, TypeScript checks, and the unit test suite
npm run lintRuns ESLint across the repository
npm run typecheckChecks TypeScript without emitting files
npm testRuns deterministic tests for contracts, parsing, ranking, and caching
npm run buildCreates a production Next.js build
npm startServes an existing production build
npm run cache:warmRefreshes every provider and bedroom segment into Turso
npm run cache:watch -- --interval-minutes=30Repeats the inventory refresh locally
npm run stress:searchExercises live search lanes against a running app

stress:search, cache:warm, and cache:watch make real upstream requests. They are intentionally excluded from npm run check and CI.

Architecture

app/
├── _components/criblist/ product UI and browser-local state
├── _components/ui/ reusable visual primitives
├── _lib/ browser utilities
├── api/apartment-search/ validated search HTTP route
├── api/cron/refresh-listings/ protected inventory refresh route
└── api/provider-brands/ provider brand enrichment route
server/
├── brand/ Context.dev provider enrichment
├── cache/ Turso inventory and refresh orchestration
└── search/ source adapters, normalization, and ranking
shared/
├── cities.ts city metadata and neighborhood catalogs
├── providers.ts city-aware provider catalog and search-lane mapping
└── search-contract.ts shared browser/server schemas
scripts/ live stress and inventory utilities
tests/ deterministic Node test suite

The browser sends one validated Preferences object to POST /api/apartment-search?source=all. The server first checks Turso when it is configured. If every requested inventory segment is fresh, it builds a deck from the stored cards. Otherwise, source adapters run concurrently, failures are isolated per source, and successful cards pass through the same quality, deduplication, preference, and provider-diversity rules. Best-effort results are written back to Turso.

The API also accepts fast, independent, craigslist, and extract source lanes for diagnostics and targeted testing. A completed Apartment Deck contains at most eight cards.

See CONTEXT.md for the domain language used throughout the codebase.

Live sources

San Francisco

  • Craigslist San Francisco
  • Brick + Timber
  • RentSFNow
  • Mosser Living
  • J. Wavro Associates
  • Rentals Inc.
  • Rentals in SF
  • Landmark Real Estate
  • ReLISTO

New York City

  • StreetEasy
  • Nooklyn
  • The Brodsky Organization
  • Stonehenge NYC
  • Craigslist New York City

Adapters begin at each publisher's current-availability page. Upstream HTML, APIs, and access policies can change without notice, so source fixes should include a focused parser regression test.

Troubleshooting

  • Search says a Context.dev key is required: confirm CONTEXT_DEV_API_KEY is set in .env.local, then restart npm run dev.
  • A cache command asks for Turso credentials:TURSO_DATABASE_URL and TURSO_AUTH_TOKEN must be configured together. They are optional for normal local searches.
  • One provider returns no cards: source failures are isolated and live sites change independently. Try broader Preferences, then check the provider's current-availability page and the corresponding adapter test.
  • npm ci reports an unsupported engine: switch to a supported LTS release; nvm use reads the repository's .nvmrc.

Deployment

Any Node.js host that supports Next.js can run Criblist with npm run build and npm start. For Vercel:

  1. Import the repository.
  2. Set CONTEXT_DEV_API_KEY.
  3. To enable persistent inventory, also set TURSO_DATABASE_URL, TURSO_AUTH_TOKEN, and a long random CRON_SECRET.
  4. Set NEXT_PUBLIC_SITE_URL when the production URL cannot be inferred from Vercel's environment.

vercel.json schedules the protected inventory route once a day at 08:00 UTC. Other schedulers must call the same route with Authorization: Bearer <CRON_SECRET>.

Contributing and security

Contributions are welcome. Read CONTRIBUTING.md before opening a pull request and follow the Code of Conduct.

Please report vulnerabilities privately as described in SECURITY.md, not in a public issue.

Data and trademark notice

Criblist is an independent discovery interface, not a rental broker or listing provider. Provider names and trademarks belong to their respective owners. Listing text, photos, and other content fetched at runtime remain subject to the publisher's terms and rights. Review those terms before operating a public deployment or adapting an adapter for another site.

License

Criblist is available under the MIT License.

About

A swipeable shortlist of live San Francisco apartment listings, powered by Context.dev.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

12 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

Criblist

the sf + nyc hunt, minus the hunting.

Live demo · CIMIT license

Criblist turns live San Francisco and New York City rental inventory into a small, preference-matched deck of apartments to review, swipe, and shortlist. It is a runnable product and a reference implementation of the Context.dev Web Extraction API.

Important

Rental availability, prices, and details can change at any time. Always verify a listing with its publisher before applying or sending money.

What it does

  • Searches fourteen live rental marketplaces and property managers across two cities
  • Filters by budget, bedrooms, bathrooms, neighborhood, laundry, pets, dishwasher, and size
  • Normalizes different source formats into one validated Apartment Card
  • Ranks and diversifies each Apartment Deck so one provider cannot dominate it
  • Stores preferences, deck progress, and saved homes in the browser only
  • Optionally keeps a persistent Turso inventory for faster repeat searches

How Context.dev is used

Criblist has four acquisition paths that share the same validation, ranking, and deduplication pipeline:

  1. The HTML API renders Craigslist search and detail pages.
  2. The Markdown API turns filtered StreetEasy result pages into a compact, parseable inventory stream.
  3. The Extract API turns property-manager inventory pages into typed listing candidates.
  4. Direct adapters read structured public inventory endpoints and page markup.

The Brand API enriches the provider list with current brand identities.

All Context.dev calls happen on the server. The API key is never sent to the browser.

Quick start

Requirements

Install and run

git clone https://github.com/context-dot-dev/crib-shortlist.git
cd crib-shortlist
npm ci
cp .env.example .env.local

Add your Context.dev key to .env.local:

CONTEXT_DEV_API_KEY=your_key_here

Then start the development server:

npm run dev

Open http://localhost:3000. A Turso database is not required for local development; without one, searches acquire listings live.

Optional persistent inventory

Turso lets Criblist serve fresh inventory immediately and refresh it outside a user request. Create a Turso database, add both credentials to .env.local, and warm it once:

TURSO_DATABASE_URL=libsql://your-database.turso.ioTURSO_AUTH_TOKEN=your_token_here
npm run cache:warm

Pass --city=sf or --city=nyc to refresh one city while developing. Add --source=<source-id> or --bedrooms=<studio|1|2|3+> to narrow a debugging run:

npm run cache:warm -- --city=nyc
npm run cache:warm -- --city=nyc --source=streeteasy --bedrooms=2

The tables and indexes are created automatically. To refresh continuously during local development, run:

npm run cache:watch -- --interval-minutes=30

The interval must be at least five minutes. Warming queries live sites and can consume Context.dev credits.

Environment variables

VariableRequiredPurpose
CONTEXT_DEV_API_KEYFor searchContext.dev HTML, Extract, image, and Brand API access
TURSO_DATABASE_URLFor persistent inventoryTurso/libSQL database URL; must be set with TURSO_AUTH_TOKEN
TURSO_AUTH_TOKENFor persistent inventoryAuthenticates Turso reads and writes; must be set with TURSO_DATABASE_URL
CRON_SECRETFor scheduled refreshesProtects GET /api/cron/refresh-listings with a bearer token
NEXT_PUBLIC_SITE_URLNoCanonical origin used for social metadata outside Vercel
CRIBLIST_BASE_URLNoTarget URL for npm run stress:search; defaults to http://localhost:3000

Never commit .env, .env.local, API keys, or database credentials. The provided .env.example contains names and comments only.

Commands

CommandWhat it does
npm run devStarts the Next.js development server
npm run checkRuns linting, TypeScript checks, and the unit test suite
npm run lintRuns ESLint across the repository
npm run typecheckChecks TypeScript without emitting files
npm testRuns deterministic tests for contracts, parsing, ranking, and caching
npm run buildCreates a production Next.js build
npm startServes an existing production build
npm run cache:warmRefreshes every provider and bedroom segment into Turso
npm run cache:watch -- --interval-minutes=30Repeats the inventory refresh locally
npm run stress:searchExercises live search lanes against a running app

stress:search, cache:warm, and cache:watch make real upstream requests. They are intentionally excluded from npm run check and CI.

Architecture

app/
├── _components/criblist/ product UI and browser-local state
├── _components/ui/ reusable visual primitives
├── _lib/ browser utilities
├── api/apartment-search/ validated search HTTP route
├── api/cron/refresh-listings/ protected inventory refresh route
└── api/provider-brands/ provider brand enrichment route
server/
├── brand/ Context.dev provider enrichment
├── cache/ Turso inventory and refresh orchestration
└── search/ source adapters, normalization, and ranking
shared/
├── cities.ts city metadata and neighborhood catalogs
├── providers.ts city-aware provider catalog and search-lane mapping
└── search-contract.ts shared browser/server schemas
scripts/ live stress and inventory utilities
tests/ deterministic Node test suite

The browser sends one validated Preferences object to POST /api/apartment-search?source=all. The server first checks Turso when it is configured. If every requested inventory segment is fresh, it builds a deck from the stored cards. Otherwise, source adapters run concurrently, failures are isolated per source, and successful cards pass through the same quality, deduplication, preference, and provider-diversity rules. Best-effort results are written back to Turso.

The API also accepts fast, independent, craigslist, and extract source lanes for diagnostics and targeted testing. A completed Apartment Deck contains at most eight cards.

See CONTEXT.md for the domain language used throughout the codebase.

Live sources

San Francisco

  • Craigslist San Francisco
  • Brick + Timber
  • RentSFNow
  • Mosser Living
  • J. Wavro Associates
  • Rentals Inc.
  • Rentals in SF
  • Landmark Real Estate
  • ReLISTO

New York City

  • StreetEasy
  • Nooklyn
  • The Brodsky Organization
  • Stonehenge NYC
  • Craigslist New York City

Adapters begin at each publisher's current-availability page. Upstream HTML, APIs, and access policies can change without notice, so source fixes should include a focused parser regression test.

Troubleshooting

  • Search says a Context.dev key is required: confirm CONTEXT_DEV_API_KEY is set in .env.local, then restart npm run dev.
  • A cache command asks for Turso credentials:TURSO_DATABASE_URL and TURSO_AUTH_TOKEN must be configured together. They are optional for normal local searches.
  • One provider returns no cards: source failures are isolated and live sites change independently. Try broader Preferences, then check the provider's current-availability page and the corresponding adapter test.
  • npm ci reports an unsupported engine: switch to a supported LTS release; nvm use reads the repository's .nvmrc.

Deployment

Any Node.js host that supports Next.js can run Criblist with npm run build and npm start. For Vercel:

  1. Import the repository.
  2. Set CONTEXT_DEV_API_KEY.
  3. To enable persistent inventory, also set TURSO_DATABASE_URL, TURSO_AUTH_TOKEN, and a long random CRON_SECRET.
  4. Set NEXT_PUBLIC_SITE_URL when the production URL cannot be inferred from Vercel's environment.

vercel.json schedules the protected inventory route once a day at 08:00 UTC. Other schedulers must call the same route with Authorization: Bearer <CRON_SECRET>.

Contributing and security

Contributions are welcome. Read CONTRIBUTING.md before opening a pull request and follow the Code of Conduct.

Please report vulnerabilities privately as described in SECURITY.md, not in a public issue.

Data and trademark notice

Criblist is an independent discovery interface, not a rental broker or listing provider. Provider names and trademarks belong to their respective owners. Listing text, photos, and other content fetched at runtime remain subject to the publisher's terms and rights. Review those terms before operating a public deployment or adapting an adapter for another site.

License

Criblist is available under the MIT License.

About

A swipeable shortlist of live San Francisco apartment listings, powered by Context.dev.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

12 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages