He already read it. All of it.
Carl doesn't check the news. The news checks in with Carl. Carl never sleeps. He drinks coffee and reads everything. He finished the internet. Now he checks nightly for new stuff. And when you drop by, he has notes.
Give Carl three topics. You know the ones. He'll brew a hot cup of what you just missed.
Carl stays up. You stay informed.
Status: early development. The scaffold is real; the product is being built change-by-change via OpenSpec — see
openspec/.
Bun + TypeScript · React SPA (Vite + Tailwind + shadcn) · Hono · Drizzle + Neon Postgres (pgvector) · Temporal · LiteLLM → Fireworks · Vercel AI SDK + Zod · Exa + Firecrawl · Langfuse
Modular monolith — one package.json, one deploy:
ui/— React SPAapi/— Hono HTTP layerworker/— Temporal workflows and source adaptersdb/— Drizzle schema and migrations
Domain vocabulary is load-bearing and lives in .agents/skills/domain-model/.
How the AI guardrails work: docs/ai-scaffolding.md.
bun install
bun run dev:ui # Vite dev server (UI); wraps itself in doppler run
bun run dev:api # Hono API; wraps itself in doppler run for DATABASE_URL; the Vite dev server proxies /api here
bun run build:ui # production build (no doppler, so it runs in CI and deploys)The homepage needs both dev:ui and dev:api running, plus a seeded dev database (below). The dev, db, and smoke scripts wrap themselves in doppler run, so they need a Doppler-configured machine.
Database — generate a migration from the Drizzle schema, then apply it:
bun run db:generate # write a migration from db/schema.ts (offline, no doppler)
bun run db:migrate # apply pending migrations
bun run db:seed # creates the dev demo user via a real signup, then loads idempotent stub data (refuses to run outside the dev config)db:seed signs up a real dev account (DEV_USER_EMAIL / DEV_USER_PASSWORD in .env.example) through Better Auth, so log in with those credentials locally to see the seeded demo topics. Auth needs a few more Doppler variables locally: BETTER_AUTH_SECRET, BETTER_AUTH_URL, GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET, GITHUB_CLIENT_ID/GITHUB_CLIENT_SECRET, and RESEND_API_KEY/RESEND_FROM_EMAIL — see .env.example for what each is for. Signup itself is open: no invite code, Google/GitHub are one-click, and email/password sits behind a "Continue with email" toggle.
Checks — run the full gate with one command (enforced on push by scripts/preflight.sh):
bun run check # biome + tsc + bun testOr run them individually:
bunx biome check .
bunx tsc -b
bun testLive smoke tests (owner-run) — exercise real flows against live services (LiteLLM proxy, Firecrawl, object storage), so they make paid calls and are not part of bun run check. Need the LiteLLM proxy up (docker compose up -d litellm) and the latest migration applied:
bun run smoke # run all smoke tests
bun run smoke:scan # just the topic-scan smoke test (ingestion + review, end-to-end)
bun run smoke:attach # just the URL-attachment smoke test (Firecrawl fetch → context → object storage)
bun run smoke:search # just the search-scout smoke test (context → LLM queries → Exa → Resources)Prompt registry (owner-run) — git is canonical for prompt wording (worker/prompts/*.md); this pushes it up to Langfuse as the production version each prompt is served from. Idempotent: an unchanged prompt creates no new version. Needs LANGFUSE_PUBLIC_KEY/LANGFUSE_SECRET_KEY set:
bun run prompts:syncThe persona for CarlNotes was inspired by Jake Van Clief. The real Jake runs Eduba, an AI training and consulting company, makes excellent videos on YouTube, and teaches AI systems over at Clief Notes. Go learn from him. Carl would.
MIT
