Private company intelligence for investors, founders, and startups. Scout uses Gemini AI to generate rich profiles for any organization or person in the startup and VC ecosystem, and persists everything it learns to a Cloudflare D1 database so repeat lookups are instant.
Search — type any company name, fund, or person. Scout queries Gemini and returns up to five matching organizations and five matching people with descriptions and metadata.
Organization detail — full profile for a company or VC firm: description, funding history (rounds, amounts, lead investors), portfolio companies (for investors), key team members, and source links.
Person detail — full profile for a founder, partner, or executive: bio, location, complete job history with dates, and source links.
AI-assisted lookup — if a name isn't in the database yet, Scout surfaces an "AI look up" flow. For organizations you can paste a website URL; for people you can paste a LinkedIn URL. These hints are forwarded to Gemini for more accurate results.
Refresh — the Edit button in the navbar triggers a fresh Gemini call for any org or person, then saves the updated record back to D1.
D1 persistence — every org and person fetched from Gemini is normalized and written to Cloudflare D1. The next request for the same slug reads from D1 instantly with no AI call needed.
scout/
├── frontend/ React + Vite + Tailwind SPA
│ └── src/
│ ├── pages/ Home, OrgDetail, PersonDetail
│ ├── components/ Navbar, SearchCommand, shadcn/ui primitives
│ ├── lib/ api.ts — fetch wrapper + localStorage org cache
│ └── types/ index.ts — all shared TypeScript types
├── worker/ Cloudflare Worker (Hono)
│ ├── schema.sql D1 table definitions (run once to provision)
│ ├── wrangler.toml Cloudflare deployment config + D1 binding
│ └── src/
│ ├── index.ts Hono app, route mounting, Env type
│ ├── helpers.ts Gemini API client + system prompts (TOON format)
│ ├── db.ts D1 read/write helpers (getOrg, saveOrg, getPerson, savePerson)
│ └── routes/
│ ├── search.ts GET /api/search?q=
│ ├── orgs.ts GET /api/orgs/:slug, POST /api/orgs/:slug/refresh
│ └── people.ts GET /api/people/:slug, POST /api/people/:slug/refresh
└── mock/ Static Node.js mock server for local dev (no Gemini quota)
├── server.mjs Mirrors worker routes using fixture data
└── data.json Fixture: Nophin, Y Combinator, Teddy Li
User navigates to /org/:slug
→ frontend: GET /api/orgs/:slug
→ worker: check D1 for slug
hit → return stored OrgDetail immediately
miss → callGemini(slug, COMPANY|INVESTOR_SYSTEM_PROMPT)
→ decode TOON response → OrgDetail
→ waitUntil(saveOrgToDB(DB, result)) ← non-blocking
→ return OrgDetail
User clicks Refresh (Edit button)
→ frontend: POST /api/orgs/:slug/refresh
→ worker: callGemini (always, bypasses D1)
→ decode → OrgDetail
→ waitUntil(saveOrgToDB(DB, result)) ← overwrites old record
→ return OrgDetail
The url= and linkedin= disambiguation params bypass the D1 cache (the hint implies the user wants a specific, possibly different result than what's stored).
Seven normalized SQLite tables:
| Table | Contents |
|---|---|
organizations |
Companies, VC firms, funds |
people |
Founders, partners, executives |
person_org_jobs |
Employment records linking people → orgs |
funding_rounds |
Fundraising rounds for companies |
funding_round_investors |
Investors participating in each round |
investments |
Investor portfolio entries (investor → company) |
sources |
Source URLs attached to orgs or people |
- Root records (
organizations,people) useINSERT OR REPLACE. - Child rows (rounds, jobs, sources) are deleted and re-inserted on every save so stale data never lingers (e.g. a team member who left).
- Secondary records that arrive nested in a response — portfolio company stubs, job org stubs, team member stubs — are saved with
INSERT OR IGNOREso a previously fetched full record is never downgraded to partial data.
# 1. Create the database (one-time)
cd worker
npx wrangler d1 create scout
# 2. Paste the returned database_id into wrangler.toml under [[d1_databases]]
# 3. Apply schema to the remote database
npx wrangler d1 execute scout --remote --file=schema.sql
# 4. Apply schema to the local dev database
npx wrangler d1 execute scout --local --file=schema.sql- Node.js 18+
- A Gemini API key (Google AI Studio)
- A Cloudflare account with
wranglerauthenticated (npx wrangler login)
npm installReturns static fixture data for Nophin, Y Combinator, and Teddy Li. No API keys required.
# Terminal 1: mock worker API on :8787
npm run dev:mock
# Terminal 2: Vite dev server on :5173 (proxies /api → :8787)
npm run dev# 1. Create worker/.dev.vars (gitignored)
echo "GEMINI_API_KEY=your_key_here" > worker/.dev.vars
# 2. Terminal 1: wrangler dev on :8787 (uses local D1 SQLite)
npm run dev:worker
# 3. Terminal 2: Vite dev server
npm run devWorker — set in worker/.dev.vars for local dev; use npx wrangler secret put for production:
| Variable | Description |
|---|---|
GEMINI_API_KEY |
Google Gemini API key |
Frontend — copy frontend/.env.example to frontend/.env.local:
| Variable | Description |
|---|---|
VITE_LOGO_DEV_PUBLISHABLE_KEY |
Logo.dev publishable key for org logos (optional) |
# Build and deploy the worker (includes D1 binding)
npm run build:worker
npx wrangler deploy --cwd worker
# Build the frontend (deploy separately to Cloudflare Pages or any static host)
npm run build:frontendThe frontend vite.config.ts proxies /api to http://localhost:8787 in dev. In production, deploy the worker and frontend to the same Cloudflare account so the Pages project can route /api/* to the worker via a custom route.
| Layer | Tech |
|---|---|
| Frontend | React 18, Vite, Tailwind CSS, shadcn/ui, React Router |
| Worker | Cloudflare Workers, Hono, TypeScript |
| AI | Google Gemini (gemini-3-flash-preview) via REST, responses in TOON format |
| Database | Cloudflare D1 (SQLite at the edge) |
| Logos | Logo.dev |
MIT © TXL. See LICENSE.