Business Finder
@leumas/business-finderLeumas Prospecting: find local businesses with weak/missing web presence (OpenStreetMap discovery + website analysis + deep-scan), score the Imperium opportunity (esp. a site shipping no schema.org...
Business Finder (@leumas/business-finder)
Leumas Prospecting: find local businesses with weak/missing web presence (OpenStreetMap discovery + website analysis + deep-scan), score the Imperium opportunity (esp. a site shipping no schema.org JSON-LD), and draft each lead a starter Imperium site. Migrated from the legacy leumas-business-site-finder tool; the node-safe SDK powers the adapter + router, the React app powers the Studio/desktop/Web dashboard.
How agents call it
| Surface | How |
|---|---|
| CLI (headless, no server/auth) | node ops/tools/run-tool/run-tool.mjs business-finder <op> '<jsonArgs>' |
| MCP client / chatbot / Leviathan | adapter business-finder → tool/functioncall (discover, analyzeSite, scoreLead, deepScan, assembleImperiumConfig) |
| Automation rule / grid cell | adapter ops as cells + action business-finder.draft-imperium-site (lead → drafted config) |
| HTTP | /api/business-finder/* (JSON, requireAuth) |
| Studio | Prospecting domain (/admin/prospecting) |
| Code | runSearchPipeline, scoreLead, assembleImperiumConfig … from @leumas/business-finder/sdk |
Run it from the CLI
node ops/tools/run-tool/run-tool.mjs business-finder discover '{"query":"plumber","city":"Chicago","state":"IL","maxResults":8}'
discover hits OpenStreetMap live — set OSM_CONTACT_EMAIL for the polite Nominatim email. Ops: discover (area → ranked leads, best = weakest site), analyzeSite ({url} → status/score + missing-schema signal), scoreLead ({lead} → Imperium-opportunity score), deepScan ({url|lead} → services/USPs/credentials for a starter brief), assembleImperiumConfig ({lead,mode} → {mode,data,seo} draft). See --describe business-finder.
/api/business-finder routes
Stateful orchestration + persistence over the SDK. Runs persist in the dynamic bf_runs collection, per-user settings in bf_settings; everything is owner-scoped (doc.owner === req.user.id || admin).
POST /search— discover + analyze + score, persist a run.GET /runs,GET /runs/:id.POST /analyze/run/:id— re-analyze every lead in a run.GET /leads/:id,PATCH /leads/:id(manual review),POST /leads/:id/deep-scan.POST /leads/:id/draft-site— draft (never publish) a starter Imperium config and store it on
the lead. GET/PATCH /settings. POST /export/:runId (csv|json). GET /health.
draft-imperium-site (the flagship flow)
POST /leads/:id/draft-site (and the identical shared action business-finder.draft-imperium-site, so it's a chainable grid cell) composes existing adapters through the registry — no cross-pack static import:
landing-copy→ AI hero / benefits / CTA copy blocks.seo→jsonLd(schema.org Organization) — the structured data the current site lacked.assembleImperiumConfig(pure) stitches them into{ id, appName, mode, data, seo, source }.
Publishing stays a manual step in the Hosting domain; this only drafts. Each adapter call is try/caught, so a missing LLM degrades to a deterministic draft rather than failing.
Studio Prospecting domain
/admin/prospecting → ProspectingSurface mounts @leumas/business-finder/app (nav id prospecting, icon [target], surface ProspectingSurface). Same React app runs on the leumas-os desktop and the Web library.
DRY reuse (do NOT duplicate)
- Discovery = OpenStreetMap (Nominatim + Overpass) in the SDK provider registry — no paid places
API, no key. mock provider for offline demos.
- Scraping/SSRF = the shared
_shared/net.jsguard (same onescraping+url-screenshotuse);
URL-fetching ops refuse private/localhost. analyzeSite/deepScan fetch with cheerio, not a browser.
- AI copy = the
landing-copyadapter; structured data = theseoadapter — both via the
injected registry, never a raw provider fetch.
- Persistence = the dynamic CRUD connector (
api.db), not bespoke storage. - Render/publish =
@leumas/uiDynamicPage +@leumas/hosting— the finder only produces the
config.
Gating
Adapter ops are free/ungated (registry-projected). The router is requireAuth on everything; PassNode feature:business-finder.run meters the network/LLM lanes (search / analyze / deep-scan / draft-site) — a no-op until a passnode_rules doc for that resource id exists. run-tool bypasses auth (no HTTP layer). No membership tier is required.