Skip to content

Repository files navigation

NicheDB

Sources in, feeds out. An open platform for databases that only ever grow: game releases, package registries, government filings, and any other public data with a big seed and daily growth. One Postgres, one Redis, and every row reachable by web, RSS, JSON, API, CLI and MCP.

The first deployment is nichedb.dev. Run your own on anything that has Postgres and Redis.

The model

Thing What it is
collection A niche: games, packages, filings.
source One adapter pointed at one upstream, fetched on its own cadence. Steam's new-releases list. The npm changes feed. EDGAR Form D filings.
item One row a source produced. Title, URL, when (with time_known and precision), tags, and the adapter's payload in data.
feed A saved query over a collection. Has a page, RSS and JSON Feed renderings, an API endpoint, and followers who are told when it changes by push, email or signed webhook.

Adapters are one file each in packages/adapters/src. One hundred and twenty-eight ship today across forty-one collections:

Collection Adapters Key needed
games steam, steam-news, igdb, igdb-catalog (every game IGDB knows, walked by id then kept current from updated_at), wikidata-games, steam-catalog IGDB only (Twitch client)
packages npm, pypi, crates, go-modules, huggingface, github-releases no (GitHub token optional)
filings edgar, federal-register, courtlistener CourtListener only
music musicbrainz, musicbrainz-catalog, discogs-catalog no
books openlibrary, gutenberg-catalog, librivox-catalog, openlibrary-catalog no
tabletop scryfall-sets, scryfall-cards no
space launch-library no
chess lichess-broadcasts no
alerts usgs-earthquakes, gdacs no
weather nws-alerts, nhc-cyclones, swpc-space-weather, eonet-events no
outages statuspage (any Statuspage host) no
extensions firefox-addons, vscode-extensions, mcp-registry no
health openfda-recalls, clinical-trials no
research arxiv, crossref no
automotive fueleconomy-catalog, nhtsa-recalls, nhtsa-complaints, nhtsa-safety-ratings no
markets iso-mic-exchanges, alpaca-corporate-actions, alpaca-news, alpaca-assets, equity-history, nasdaq-halts, ecb-fx-rates, sec-fundamentals Alpaca (APCA_API_KEY_ID, APCA_API_SECRET_KEY, APCA_FEED iex or sip); SEC wants CONTACT_EMAIL in the user agent
crypto coingecko-assets, crypto-pairs keyless (COINGECKO_API_KEY optional; CRYPTO_PROXY_URL for Binance.US from a datacenter)
crime socrata-crime, uk-police-crime, fbi-crime-estimates, police-updates (39 California police feeds; source coverage) FBI only (free api.data.gov key)
public-money usaspending-awards, ocds-tenders, ted-notices no
housing uk-land-registry, freddie-mac-rates, building-permits, ruuster (agent saved searches) no
jobs bls-series, eurostat, warn-layoffs, agenticjobs no
ai-incidents rogue-ai-incidents, rogue-ai-research, aiid-reports no
news newsfeed, gdelt, rssamplifier, brisk, news-channels no
domains ntld-totals, ntld-tlds, ntld-launches, ntld-changes no
podcasts podcasts, p0dcasters, podcastindex-catalog no
aviation faa-nas-status, aviation-hazards, aviation-metar, ntsb-accidents, adsb-flights no (NTSB needs mdbtools + unzip, in the Dockerfile)
water nwps-river-gauges, coops-water-levels, drought-monitor, ndbc-buoys, nws-surf-zone no
consumer-finance cfpb-complaints, fdic-institutions, fdic-structure-changes no
deals slickdeals, dealnews, dealcatcher, bensbargains, reddit-deals no
sports espn-catalogue, espn-schedule, espn-live, espn-plays, livetennis, sportsdb-tv, sportsdb-leagues, sportsdb-teams Live Tennis; TheSportsDB (SPORTSDB_API_KEY, the shared key 3 returns one row per query); ESPN is keyless (SPORTS_PROXY_URL for cloud egress)
screen tmdb-releases, tvmaze-schedule, tvmaze-catalog (every show TVmaze knows, CC BY-SA, walked once then kept current from /updates/shows), anilist-airing, imdb-ratings, wikidata-films, kitsu-anime, thetvdb-catalog (every series on TheTVDB, THETVDB_API_KEY, attribution link required) TMDB only
channels iptv-org-channels no
saas saasrow, opensaas (nichedb.dev first; any service serving /.well-known/opensaas.json) no
marketplace d0rz, bl0ggers no
ai-media aiornot no
forums tsbb no
coupons c0upons (what c0upons.com took in on its own: submitted codes and its r/couponcodes listings; rows it copied from deals are not read back) no
dht bittorrented-dht (adult material left out) bittorrented.com's Supabase URL and publishable key (BITTORRENTED_SUPABASE_URL, BITTORRENTED_SUPABASE_KEY); paused without them
hosting findhost, buyvps, vultr-plans, linode-types, scaleway-instances, ovh-vps, storefront (WHMCS, Blesta and WooCommerce order forms), lowendbox, openserver; hetzner-plans, digitalocean-sizes, upcloud-plans no for the first eight (FindHost data is CC BY 4.0: credit FindHost, findhost.app; OBSCURA_MCP_URL optional for JavaScript-only shops); Hetzner (HETZNER_API_TOKEN), DigitalOcean (DIGITALOCEAN_TOKEN) and UpCloud (UPCLOUD_USERNAME, UPCLOUD_PASSWORD) each need a read-only credential and stay paused without one
threats openthreat (ThreatCrush first; any reporter serving /.well-known/openthreat.json) no
profiles openprofiles (p0dcasters and OutreachGraph listings first; any app that lists the OpenProfile.md files it serves), sportarr-persons (Sportarr's 111k names, kept only when Wikidata knows the person as a human with a sport, with their socials), sportsdb-players no

The way out (OpenSaaS)

nichedb serves its own OpenSaaS descriptor at /.well-known/opensaas.json: the plans as the checkout sells them (prepaid terms, none renews itself) and the actions that are real here, each as the page a person opens and the endpoint an agent calls with an OpenAccess bearer: subscribe (POST /api/v1/billing/subscribe, answers pending with the checkout page), cancel (POST /api/v1/billing/cancel, one click, today, nothing refunded), unsubscribe (a page: /settings, where the follows that send mail are turned off), export (POST /api/v1/account/export, one JSON document at once), delete (POST /api/v1/account/delete, confirmed by a link to the account email, immediate, nothing kept). The same doors as pages: /account/billing, /account/export, /account/delete. The opensaas adapter reads other services' descriptors into the saas collection, one row per plan with the service's own steps and confirm for the way in and the way out shown beside each other.

People (/c/profiles)

One entry per person, assembled from every app that serves their OpenProfile.md. The openprofiles adapter pages each app's listing (/api/openprofiles?since=…), fetches every document from the host that listed it, and merges documents that share an identity key (the Web URL, any Accounts URL, Email, DID) into one profile; a shared name alone never merges. The page is /c/profiles/<slug>-<id> (the id resolves, the name is cosmetic, a wrong name 301s) or /c/profiles/<handle> once the owner took one, and …/openprofile.md next to it is the file, text/markdown, CORS open, with <link rel="openprofile"> on the page.

A person claims their entry with This is me (proven by the email the profile lists, or a link back to the page from their site or show; admins may claim for an email) and edits it from the page, PUT /api/v1/profiles/<ref> (JSON overlay or a whole text/markdown file), nichedb profile edit, or the update_profile MCP tool, with a session, an API key, or an OpenAccess token carrying openprofile:edit. What the owner writes is an overlay that wins over every source and survives every re-read; a section written as none is removed. The Broadcast and Guest sections (OpenBroadcast, OpenGuest) are what the podcasters and guests feeds cut on. | directory | outreachgraph | no | | sites | opensite | no |

Moderators

A niche can have moderators: people who review what is suggested for its collection without operating it. Anyone signed in applies at /<niche>/moderate (three questions); the application is a claim marked with the role, listed on /admin/knowledge with every other claim, and only an admin decides it. An approved moderator is a niche member with a share cap of zero and no score row: moderating earns nothing and is not a rung on the ladder. More than one person may moderate a niche. Moderators see the suggestion queue at /admin/submissions filtered to the collections their niches feed, approve into that collection only, and get the Queue link in the nav.

Sites (/c/sites)

One record per page, read the way OpenSite says: the title, the line, the picture and the kind a card is drawn from, the canonical address it is keyed by, the author, the feeds, and the og:, twitter: and JSON-LD tags verbatim. The opensite adapter walks each house site from its front page and sitemap, a hundred pages a run, honouring robots.txt and a site's own /.well-known/opensite.json. Any other address is read the moment somebody pastes it at /c/sites/add, which then shows every tag and the card as X, Slack, iMessage, Discord, LinkedIn and WhatsApp would draw it; the same read is POST /api/v1/sites { "url" }, and GET /api/v1/sites?url= answers the kept record or reads the page when it is older than an hour. A whole list, up to ten thousand addresses pasted or uploaded on the same page (or POST /api/v1/sites/bulk with JSON urls or a text body), becomes a source of the submitter's: the worker walks it 250 pages a run, a minute apart, and /s/<slug> shows the progress; a signed-in account is needed, since a list makes the deployment fetch on a schedule. The page is /c/sites/<host>/<path>, query string and all, because a page's identity can be in its query.

House aggregators

Ten of the sites we run publish a public feed or API of their own, and each is read here through it, keyless, the way any other reader would. The endpoint is the one checked live on 2026-09-12 (c0upons on 2026-09-13); the slug is the seeded source.

Site Endpoint Collection Source slug Kinds
p0dcasters.com /opml (every show: feed, site, title) podcasts p0dcasters-shows show
saasrow.com /api/v1/products (offset paged, 100 a page) saas saasrow-products product
d0rz.com /asks/rss.xml, /offers/rss.xml marketplace d0rz-marketplace ask, offer
bl0ggers.com /asks/rss.xml, /offers/rss.xml marketplace bl0ggers-marketplace ask, offer
aiornot.vote /rss.xml, /rss/featured.xml, /rss/trending.xml ai-media aiornot-media submission
agenticjobs.work /api/v1/jobs (offset paged, 100 a page) jobs agenticjobs-postings job
outreachgraph.com /api/v1/public/directory (cursor paged, 200 a page; companies and sites by domain, people only when self-published) directory outreachgraph-directory company, site, person
tsbb.dev /api/v1/forums, then /f/{slug}/feed.xml per forum forums tsbb-topics post
c0upons.com /api/coupons (offset paged, 200 a page, store joined in; rows with source: nichedb skipped) coupons c0upons-coupons coupon
bittorrented.com PostgREST rpc/browse_dht_torrents on the site's own Supabase, by date from a cursor, 500 a page (the site has no submit or export endpoint) dht bittorrented-dht torrent
c0ncerts.com none yet: /api/events answers 501 "coming soon" and /api/v1/events 404s

saasrow's /api/v1/listings is per-account and needs a key, so only the public products directory is read. A submission aiornot carries on more than one feed is stored once and tagged with each feed. tsbb's cross-board /api/v1/latest does not say which forum a topic is in, which is why the walk is per forum.

Ruuster housing searches

ruuster-san-jose-homes reads the Real Estate Experts / Talar Davoudi saved search hourly and feeds /f/san-jose-homes. The search asks for San Jose houses that are Active or Coming Soon, with 2+ bedrooms, 1+ bathrooms, 750+ square feet, a 4,500+ square foot lot and a build year of 2000 or later. Ruuster calls the lot filter lotSizeAcresMin but accepts square feet there; property records return acres, and NicheDB keeps both units. Results and dimensions are upstream observations; conflicting MLS values are not silently corrected.

Create another ruuster source with its savedSearchUrl, pages (20 by default, ten records per page) and currency (USD or CAD). Tracking parameters are removed and repeated status filters are combined. Public detail requests add photos, property facts and MLS attribution. Syndicated copies are joined by MLS listing number and address. Hidden-address and deleted records are omitted. The worker's detail budget and deadline are respected, with a cursor for unfinished pages; completed walks start from page one on the next refresh.

Items retain the last status observed while matching the search. A property disappearing from a search is not evidence that it sold, and this source does not revisit every historical listing after it leaves the search.

Run bun run ingest ruuster-san-jose-homes against the configured database, or export without a database with bun scripts/scrape-ruuster.js > properties.json. The export also accepts a saved-search URL and an optional new output filename as positional arguments. Ruuster's terms restrict automated access without written consent, and its IDX notice limits reuse; deploying this adapter does not grant redistribution rights: https://realestateexperts.ruuster.com/terms-of-service.

MusicBrainz web-service requests share a process-wide queue with at least 1.1 seconds between requests. A 429 or 503 retries up to three times with 5, 15 and 45 second backoff, honoring longer Retry-After values, including HTTP dates. A zero retry hint cannot cause an immediate retry. This queue covers one worker process; deployments sharing an egress IP across multiple processes need coordination across those processes as well.

Enrichment

After ingest, every item is enriched by the enrichers that apply to it (packages/enrichers/src), and the results live on the item under enrichment.<name>:

Enricher Adds Default on for
youtube top videos (trailers, official audio, webcasts); Data API key optional games, music, tabletop, space, chess, books
wikipedia the article's lead paragraph and picture games, music, books, space, tabletop, hosting
github-repo stars, forks, topics, licence, language, last push packages, extensions
npm-stats last week's downloads packages
sec-company tickers, exchange, industry, state, website of the filer filings
company-ticker the listed company behind a hosting provider: ticker and CIK from the SEC's company list, matched on name hosting
developer the official CLI and its install commands (read off the vendor's guide, or matched by domain in Homebrew, npm and the Terraform registry), API docs, Terraform provider, status page hosting
semantic-scholar TL;DR, citation counts, open-access PDF (key optional) research
openlibrary-work description and subjects books
opengraph the page's own preview image and description most collections
tmdb-artwork poster, backdrop, synopsis, rating and TMDB id for a title that came in from the IMDb dumps with none (TMDB_API_KEY) screen

A feed's enrichers list picks which of these it shows; absent means the collection's defaults. The feed builder exposes them as checkboxes. Items are enriched once, newest first with a fair share per collection (ENRICH_PER_RUN a tick, ENRICH_PER_COLLECTION of them from any one collection), and a missing image, summary or tags are filled from whatever the enrichers found while the source's own words always win.

Run it

git clone https://github.com/profullstack/niche-db && cd niche-db
bun install
docker compose up -d db redis          # or point at your own Postgres 16+ and Redis
cp .env.example .env                    # DATABASE_URL, SITE_URL, CONTACT_EMAIL at minimum
bun run build:client
bun run dev                             # web + worker in one process on :3000

Migrations apply themselves on boot. Every collection, its default sources and its feeds are seeded on first boot; sources whose adapter needs a credential the deployment lacks are created paused. The first account to sign in is an admin.

bun run ingest [slug ...] runs sources from a terminal without Redis. bun test runs the suite against an in-process Postgres (PGlite), so it needs no server.

Surfaces

  • Web: a PWA. /sources manages sources (status, last run, next run, run now, pause, edit config). /feeds/new builds a feed with a live preview. /settings holds passkeys, API keys and notifications.
  • RSS / JSON Feed: /f/<slug>.rss, /f/<slug>.json.
  • API: /api/v1, documented at /docs/api. Reads need no key.
  • CLI: npm i -g @profullstack/nichedbnichedb. Documented at /docs/cli.
  • MCP: /mcp (Streamable HTTP, stateless) or nichedb mcp over stdio. Documented at /docs/mcp.
  • llms.txt: /llms.txt describes the whole deployment in one document.

Accounts and money

Magic link and passkey only; there is no password column. API keys are minted from settings and shown once.

Free pages and feeds carry one CrawlProof ad and a tracker (CRAWLPROOF_AD_SLOT, CRAWLPROOF_SITE_ID). Two tiers buy them away, both through CoinPay:

  • Premium — $1 a day, $30 a month or $300 a year (/premium). No ads, no tracking, the members' Lounge, 1,000 credits a month to give awards with, a badge, six themes and five app icons, early access to new collections, 30,000 API requests an hour, unlimited feeds, own sources, and the metered vehicle lookups included. /premium carries a line-by-line comparison with Reddit Premium; GET /api/v1/premium is the same thing as JSON. See docs/premium.md.
  • Pro — $120 a month (/pro). Everything Premium has, plus 120,000 API requests an hour and a crawl pass for the whole term (GET /api/v1/crawl-pass) so a member's own agents walk through the paywall.

Referral links give the new customer 20% off and the referrer 60% of the first payment.

Training crawlers (GPTBot, ClaudeBot, CCBot, meta-externalagent…) get a 402 with an x402 offer and buy a pass at /crawl: $1 a day for everything, and the more a buyer has paid here the less a day costs (CRAWL_LOYALTY, default 20% off after $10, 40% after $50, 60% after $100; every sale is a row in crawl_sales). A pass may switch ads or tracking off for its own requests with ?disable=ads,tracking. People, search engines and retrieval crawlers pass through untouched.

Knowledge Influencers

Every collection here is also a niche someone can operate. A Knowledge Influencer is a person who knows how an industry actually works and supervises the agents building software, data and promotion for it — no code required. They start at 20% of what the niche makes and climb to 80% as verified contribution accumulates.

/opportunities lists the niches looking for one. /<niche> is the niche's public page (plus skill.md and manifest.json for agents), /@<handle> is an operator's profile, /dashboard/niches is their own view, and /admin/knowledge is where claims and contributions are verified.

When an agent gets stuck on something only a person who has done the job can settle, it asks: the question lands on the operator's dashboard, the answer becomes niche knowledge and a scored contribution, and Chovy is told. Saying "not enough context" is a first-class answer that costs nothing, because a guess that gets verified is worse for the niche than an open question. See docs/agent-questions.md.

Score comes only from contributions somebody verified, and volume does not buy it: repeated submissions of a type pay less each time, anything claiming a customer or a payment needs an outside reference, and a duplicate books once. The ladder lives in contribution_tiers, so a deployment can tune it. What a niche earns is divided at the moment it settles, using the shares in force right then, so a tier that moves tomorrow never re-prices yesterday's sale (docs/revenue-ledger.md). See docs/knowledge-influencers.md and docs/revenue-share.md.

Stack

Bun, Hono (server-rendered JSX), Postgres via Bun's native driver, BullMQ on Redis, Biome. bun apps/worker/src/enrich-cli.js [n] runs enrichment from a terminal. Shared Profullstack modules: @profullstack/x402-gateway, @profullstack/emailer, @profullstack/coinpay, @profullstack/referrals, @profullstack/api-key-manager, @profullstack/autoblog (signed webhooks), @profullstack/favicon-generator.

Brand assets

brand/logo.svg is the editable SVG master. Run bun run build:brand to crush it into favicon.svg with SVGCrusher, then run the installed fav CLI to regenerate apps/web/public/icons. The build also updates /logo.svg, /favicon.svg, member palettes, and maskable app icons. SVG is preferred in the page and app manifest; PNG and ICO remain fallbacks. Preview everything in brand/preview.html.

SVGCrusher's initial CLI release is bundled in vendor/ so this command works from a clean checkout before an npm release. Its standalone source lives in profullstack/svgcrusher.

Configuration

Everything is read once from the environment in packages/config/src/index.js; .env.example lists every variable. Production secrets live in the nichedb--prod logicsrc vault:

bun run secrets:pull     # logicsrc teams pull profullstack nichedb prod --env .env
bun run secrets:push     # after adding a key locally

ROLES=web,worker runs everything in one container; ROLES=worker on a second service splits ingestion out when one instance stops being enough.

Deploy

One Dockerfile, one Railway service, plus Railway's Postgres and Redis. railway.json sets the healthcheck at /healthz. Set SITE_URL to the public origin before anyone registers a passkey: the passkey relying-party id is derived from it.

Adding an adapter

import { defineAdapter } from '@nichedb/core/adapter';

export const example = defineAdapter({
  name: 'example',
  title: 'Example API',
  collection: 'packages',
  description: 'What it watches, in one paragraph.',
  kinds: ['thing'],
  cadenceMinutes: 30,
  configFields: [{ key: 'topic', label: 'Topic', required: true }],
  defaultSources: [{ slug: 'example-default', name: 'Example', config: { topic: 'all' } }],
  async pull({ config, cursor, http, log, budget, deadline }) {
    const page = await http.json(`https://example.com/api?topic=${config.topic}&since=${cursor.since ?? ''}`);
    return {
      items: page.results.map((r) => ({ externalId: r.id, kind: 'thing', title: r.name, url: r.url, publishedAt: r.at, tags: r.tags, data: r })),
      cursor: { since: page.next },
      note: `${page.results.length} things`,
    };
  },
});

Register it in packages/adapters/src/index.js. The core normalises items, hashes them so unchanged rows cost no write, writes in batches, records the run and reschedules.

Streaming a dump

A source that reads a multi-gigabyte file (MusicBrainz, Discogs, Open Library, Podcast Index) cannot return one array: the file does not fit in memory and the walk does not fit in one run. Instead pull yields batches and the core drains them one at a time, writing each and saving its cursor before reading the next. pull is an async * generator (or returns { items: <async iterable> }, which is the same thing), each yielded value is { items, cursor }, and the generator's return value is the run's { cursor, note, nextInMinutes }.

import { dumpDir, gzipLines } from '@nichedb/core/dump';

export const example = defineAdapter({
  name: 'example-dump',
  // ...
  cadenceMinutes: 60,
  budgetMs: 60 * 60_000,
  async *pull({ cursor, http, log, deadline }) {
    const version = await http.text('https://example.com/dumps/LATEST');
    const skip = cursor.version === version ? Number(cursor.skip) || 0 : 0;
    if (cursor.version === version && cursor.done) return { cursor, note: 'unchanged' };

    const dir = await dumpDir('example');
    const file = `${dir}/${version}.ndjson.gz`;
    const dl = await http.download('https://example.com/dumps/latest.ndjson.gz', file);
    if (!dl.complete) return { cursor: { version, skip }, note: 'download in progress', nextInMinutes: 1 };

    let n = skip;
    let batch = [];
    for await (const line of gzipLines(file, { skip })) {
      n += 1;
      batch.push(toItem(JSON.parse(line)));
      if (batch.length === 500) {
        yield { items: batch, cursor: { version, skip: n } };
        batch = [];
        if (Date.now() > deadline) return { cursor: { version, skip: n }, nextInMinutes: 1 };
      }
    }
    if (batch.length) yield { items: batch, cursor: { version, skip: n } };
    return { cursor: { version, skip: n, done: true }, note: 'complete' };
  },
});

The rules:

  • budgetMs is the run's wall-clock budget and replaces INGEST_RUN_DEADLINE_MS (4 minutes) for this adapter. The reaper window and the BullMQ lock widen to the largest budget declared, so an hour-long budget is safe to declare. The core hands it back as deadline; check it between batches and return. A batch written past the deadline ends the run from the core's side too, rescheduling in a minute, but the adapter checking first is what keeps a run inside its lock.
  • Each batch's cursor is the position after that batch's items (a line count, a byte offset, a last id) plus whatever identifies the file (a dump date, a version, a LATEST value), so a new upstream file resets the walk. It is saved as soon as the batch is in the table. At-least-once delivery is fine: a batch re-written after a crash is an idempotent upsert on (source, externalId) and unchanged rows cost nothing.
  • return the final { cursor, note, nextInMinutes }. nextInMinutes: 1 keeps an unfinished walk moving between cadences; done: true in the cursor lets the next run short-circuit when the upstream file has not changed.
  • http.download(url, path, { timeoutMs, headers, onProgress }) streams to disk and resumes with Range from whatever is already there (append on 206, restart on 200, 416 means whole). It returns { path, bytes, complete }; on complete: false, return and call it again next run. The user agent is sent; Podcast Index refuses requests without one. Discogs ignores Range and rate-limits hard: expect a restart and keep requests to a handful an hour.
  • dumpDir(name) is where the file goes: under INGEST_DATA_DIR when set (mount a volume there), else the OS temp dir, which a redeploy wipes. The cursor is the walk; the directory is a cache.
  • Readers: gzipLines(path, { skip }), xzLines(path, { member, skip }) (MusicBrainz: member: 'mbdump/artist', needs xz on the host), tsvJsonLines(path, { skip }) for Open Library's five-column rows (each record carries its lineNo), lineOffsetReader(path, { offset }) for a plain file you want to seek in, untar(path, dir) and sqliteRows(dbPath, sql) for Podcast Index. Every reader yields every line, so skip + lines read is the file position. Skipping into a compressed file re-inflates from the top (seconds per gigabyte for gzip, slower for xz); a plain-file offset is a seek.

License

MIT

Geographic filtering and scanner context

Use ?lat=41.88&long=-87.62&radius=10 across located collections, searches and feeds. Geographic queries documents the API, CLI/MCP flags, permissioned scanner catalogs, coverage semantics and migration.

About

Sources in, feeds out. An open platform for ever-growing databases of real-time public data: web, RSS, API, CLI and MCP on one Postgres.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages