SOUL.md
May 25, 2026 · View on GitHub
Marketing landing page for budi — the open-source local-first cost tracker for AI coding agents. Lives at getbudi.dev. The product itself lives in siropkin/budi; the cloud dashboard (different subdomain: app.getbudi.dev) lives in siropkin/budi-cloud.
This repo is the public face, not the product. Keep it small, fast, and static-friendly.
Product boundaries
| Domain | Repo | What lives there |
|---|---|---|
getbudi.dev | this repo (siropkin/getbudi.dev) | Marketing site: hero, features, local-first story, install flow, dashboard CTA |
app.getbudi.dev | siropkin/budi-cloud | Authenticated cloud dashboard (Next.js + Supabase) |
| Open-source CLI / daemon | siropkin/budi | Rust workspace: transcript-tailing daemon, CLI, core business logic |
| Cursor / VS Code extension | siropkin/budi-cursor | TypeScript extension targeting VS Code and Cursor (one VSIX, host-aware scope) |
| JetBrains plugin | siropkin/budi-jetbrains | Kotlin sibling plugin for IntelliJ-platform IDEs — status-bar widget published to the JetBrains Marketplace Beta channel (0.0.1-beta.x) |
| Homebrew tap | siropkin/homebrew-budi | brew install siropkin/budi/budi formula |
Do not mix cloud dashboard code, product features, or anything that touches user data into this repo. This is a static marketing surface only.
Stack
Chosen in #2 (Astro 5 at the time, upgraded in-place to Astro 6 — same static-first contract): Astro 6 + Tailwind CSS v4, fully static output.
- Astro is static-first: the only JS that ships is what a component explicitly opts into (here, a small clipboard handler on
CopyableCommand). This matches the "a marketing site that takes 4 seconds to render is not a good ad" rule below. - Tailwind v4 is configured CSS-first in
src/styles/global.cssvia@theme— notailwind.config.js, no PostCSS pipeline. Theme tokens (--color-*,--font-*) auto-generate matching utilities likebg-surface-2,text-fg-muted,border-accent. Avoid arbitrary[--var]syntax in templates — in v4 it does not wrap invar()and silently breaks. - No React/Vue/Svelte runtime on the critical path. If a section ever needs interactivity beyond trivial vanilla JS, add a single island rather than a whole framework.
Project layout
src/
layouts/Base.astro # <html>, head, header (with mobile-nav <details> disclosure for the sections hidden below the sm: breakpoint), footer, skip link, OG/Twitter meta, JSON-LD seam (via <JsonLd />), icons, Vercel Web Analytics
components/
CodeTabs.astro # generic tabbed code-block switcher (accepts an array of {key, label, icon?, code, note?} tabs). Wraps CopyableCommand. Used directly for VSIX install tabs, and indirectly via OsInstallTabs for OS-switched install commands
CopyableCommand.astro # one-click-copy install block (hero + compact variants)
OsInstallTabs.astro # macOS / Linux / Windows install tabs; thin wrapper around CodeTabs that adds client-side OS auto-detection
EditorInstallCards.astro # VS Code / Cursor / JetBrains install cards (logo + name → marketplace link → command — simplified in #121)
GitHubIcon.astro # shared GitHub mark inline SVG — used by Base.astro (footer) and index.astro (hero CTA)
JsonLd.astro # escapes and emits a single <script type="application/ld+json"> block — used by Base.astro (SoftwareApplication) and index.astro (FAQPage)
lib/
anchors.ts # single source of truth for in-page section anchor IDs — imported by Base.astro (header nav, mobile-nav disclosure #119) and index.astro (section ids + cross-section links) so the audit-build invariant (#8 "every <a href='#X'> has a matching id") cannot drift via grep
pages/
index.astro # landing page: hero → features → providers → compare → privacy → install → dashboard → FAQ (see "Pages" below for what each section ships)
404.astro # static "not found" page, noindex, linked back to /
styles/global.css # Tailwind v4 import + @theme tokens + base layer
public/
favicon.svg # SVG favicon, source of truth for icon generation
apple-touch-icon.png # 180×180 (generated from favicon.svg)
icon-192.png, icon-512.png # PWA-size icons (generated from favicon.svg)
og.png # 1200×630 social preview (generated, see scripts/generate/)
robots.txt # allow-all + sitemap pointer
.well-known/security.txt # RFC 9116 contact + policy pointer (auto-served from public/)
scripts/
build/ # chained by `npm run build` after `astro build`; non-zero exit blocks the deploy
audit-build.mjs # post-build SEO / anchor / icon audit, runs after every `astro build`
generate-csp.mjs # post-build CSP generator: hashes inline <script> blocks in dist/ and writes the Content-Security-Policy header into vercel.json
generate/ # manual one-shot generators; NOT chained from build, NOT invoked by CI
generate-assets.mjs # PNG/OG generator (resvg + cached fonts)
fonts/ # downloaded on first run and cached here (gitignored); Inter + JetBrains Mono TTFs used by the OG generator
test/ # `node --test test/*.test.mjs` — zero-dep unit tests for the build scripts
audit-build.test.mjs # fixture-based negative tests for every audit invariant
generate-csp.test.mjs # CSP hash extraction + vercel.json rewrite tests
generate-assets.test.mjs # icon renderer smoke test + OG SVG content assertions
helpers/ # fixture builders (temp dist, minimal PNGs) and subprocess runner
astro.config.mjs # sitemap (404 filtered) + tailwindcss vite plugin + static output
vercel.json # static output, security headers (HSTS + CSP + frame/perm/referrer), HTML short-TTL, long-lived /_astro + image caches
lychee.toml # external-link health-check config (CI only)
lighthouserc.{desktop,mobile}.json # Lighthouse CI budgets enforced against Vercel preview deploys
Build & dev
npm install
npm run dev # local dev server at http://localhost:4321
npm run build # astro build → scripts/build/audit-build.mjs → scripts/build/generate-csp.mjs
npm run preview # serve dist/ locally
npm run check # astro check (TS + template diagnostics)
npm run lint # eslint . (unused imports, dead code, stray console.log)
npm run format # prettier --write .
npm run format:check # prettier --check . (what CI runs)
npm test # unit tests for the build scripts (node --test, zero deps)
npm run audit # re-run the post-build audit against an existing dist/
npm run csp # re-run the CSP hash generator against an existing dist/
npm run generate-assets # regenerate public/og.png + icon PNGs (see scripts/generate/generate-assets.mjs)
Node 22.12+ (Astro 6 minimum; CI pins node-version: 22). CI and Vercel both use the npm run build target, so both gating scripts (audit-build.mjs, generate-csp.mjs) run on every deploy.
Scripts are grouped by when they run: scripts/build/ is everything npm run build chains after astro build (gates the deploy on non-zero exit); scripts/generate/ is manual one-shots that are not invoked by CI. Each file carries a header comment documenting when it runs, what it produces, its exit-code contract, and how to reproduce a failure locally. Start there when triaging a red build:
scripts/build/audit-build.mjs— post-build SEO / structured-data / anchor / image / sitemap / icon audit. Enumerates 13 invariants in its header (per-page checks #1–10, build-wide checks #11–13).scripts/build/generate-csp.mjs— post-build inline-script hashing intovercel.json'sContent-Security-Policyheader.scripts/generate/generate-assets.mjs— manual one-shot OG + app-icon PNG generator. Not chained frombuildand not invoked by CI.
Content principles
The site should read the way the Reddit posts read — first-person, specific, honest about what budi is and isn't. Not marketing-agency copy.
- Tone: plain, technical, first-person where appropriate. No "revolutionize", no "seamless", no "empower".
- Numbers: use real numbers from real usage where possible (e.g. "136K messages, $6,154 in a month, 5,508 Haiku calls I never asked for").
- Privacy: lead with it. Prompts, code, file paths, and email addresses never leave the machine. Only opt-in numeric aggregates go to the cloud.
- Install: the primary CTA is always
brew install siropkin/budi/budi && budi init. Install is one command. Say so. - No dark patterns: no cookie walls beyond what's strictly required, no email-gated downloads, no "request a demo" when a binary is one command away.
Pages
Intentionally a single marketing page plus a 404 — deep docs, changelog, and pricing stay out of this repo.
/— section order (matches the in-source{/* N. NAME */}comments and the header nav anchors):- Hero — tagline,
OsInstallTabsinstall command (macOS / Linux / Windows), GitHub / Dashboard / Compare nav row, and abudi stats projectterminal shot. Mobile/tablet (< lg) reorders to terminal-first, then install + nav (#100). - Features (
#features) — 4 feature cards, a paired Cursor / VS Code status-bar mock figure, and abudi sessions <id>terminal shot. - Providers (
#providers) — provider coverage matrix inside a<details open>(opened by default since #118) with a Copilot Chat cost-accuracy sub-state breakdown card. - Compare (
#compare) — honest "why not just X?" alternative table; the Budi row is highlighted. - Privacy (
#privacy) — three-column "never leaves / can optionally leave / how to enable" contract, plus the#analyticsdisclosure anchor linked from the footer. - Install (
#install) — secondOsInstallTabs, 4-step after-install verifier (budi init/budi integrations install/budi doctor/budi status), and the#editor-extensionpicker rendered byEditorInstallCards(logo + name → marketplace link → command — simplified in #121). A<details>block exposesOther install methods(bundled VSIX, manual VSIX in VS Code / Cursor, daemon-from-source). - Dashboard (
#dashboard) — dashboard preview mockup with CTA linking toapp.getbudi.dev. - FAQ (
#faq) — five expandable Q&A items (first one open by default); the FAQ JSON-LD is emitted fromindex.astrovia<JsonLd />forFAQPagerich results.
- Hero — tagline,
- Header nav (in
Base.astro) keeps Install always visible; Compare / Privacy collapse belowsm:, Providers / Dashboard / FAQ collapse belowmd:. A<details>"Sections" disclosure surfaces the hidden anchors on mobile (#119). /404— static "not found",noindex, linked back to/. Excluded fromsitemap-index.xmlbyastro.config.mjs.
Deep reference links out to siropkin/budi (README, releases) or app.getbudi.dev. No /docs, /pricing, or /changelog page is planned — adding one re-opens the docs-drift problem we deliberately avoid by keeping a single surface.
Deploy
Deployment target: Vercel, auto-deploy from main (per ADR-0087 §3). vercel.json pins framework: "astro", output dir dist, and sets security + cache headers. Domain getbudi.dev is separate from app.getbudi.dev (which serves budi-cloud) — do not share edge config, cookies, or analytics scopes between them.
CI
Two GitHub Actions workflows in .github/workflows/ gate every PR:
ci.yml— on every PR and push tomain. Five jobs run in parallel against a freshnode_modulesso a red signal surfaces fast:format(npm run format:check),lint(npm run lint— ESLint flat config covering Astro + TS, catches unused imports / dead code / strayconsole.log),typecheck(astro check),test(npm test), andbuild(npm run build, which chainsastro build→scripts/build/audit-build.mjs→scripts/build/generate-csp.mjs). A sixthlink-checkjob needsbuildand runs lychee against the builtdist/usinglychee.tomlto flag broken outbound links. Internal anchor + SEO + image invariants are the audit script's job; lychee is for external URLs only. When CI fails on the build step, read the script header comment inscripts/build/<file>.mjsfor the exit-code contract and the local repro recipe.- Dependency hygiene:
.github/dependabot.ymlopens grouped monthly PRs for npm andgithub-actions. Minor/patch bumps land as a single PR per ecosystem; majors come through individually so each gets review. - Pre-commit hint (opt-in, no enforced hook): run
npm run format:check && npm run lint && npm run checkbefore pushing — these three are the gates that catch the most "the only thing red is whitespace" surprises on PR. AI agents working in this repo must runnpm run format(auto-write) before every commit that touches.astro,.ts,.tsx,.mjs,.js,.css,.json, or.md— the project intentionally has no husky hook, so the responsibility falls on the committer. Skipping it produces a noisy "Apply prettier formatting" follow-up commit. lighthouse.yml— on non-draft PRs from the same repo only (fork PRs skip because they can't read the Vercel bypass secret). Resolves the Vercel preview URL from the Deployments API, runs Lighthouse CI desktop + mobile against it usinglighthouserc.{desktop,mobile}.json, and upserts a single sticky PR comment with the scores. The gate is median-of-3 ≥ 0.95 on performance, accessibility, and best-practices. SEO is collected and surfaced in the comment but intentionally not asserted — Vercel preview deploys shipx-robots-tag: noindex, which caps the SEO score below the gate regardless of real SEO quality. Static SEO invariants are enforced byscripts/build/audit-build.mjson every PR instead.
SEO and social
og.png$ (1200 \times 630) \text{and} \text{the} \text{app}-\text{icon} \text{PNGs} \text{are} \text{generated} \text{from} $scripts/generate/generate-assets.mjsusing resvg + cached TTF fonts. They are committed topublic/so the Astro build pipeline does not need a font download on every CI run. Regenerate withnpm run generate-assetsonly when the brand/copy on those assets changes.Base.astroships the full OG/Twitter card set, canonical URL, theme-color, and a JSON-LDSoftwareApplicationblock. Thenoindexprop onBase.astrois the seam for per-page robots hints (used by404.astro).- The sitemap is generated by
@astrojs/sitemapwith the 404 page explicitly filtered out.robots.txtis allow-all and points at/sitemap-index.xml.
Analytics
Vercel Web Analytics is wired in Base.astro via @vercel/analytics/astro and counts anonymous page views only: cookieless, IP hashed daily, no prompts, no code, no session replay, no cross-site tracking. The #analytics anchor on / (inside the privacy section) discloses this in plain English and is linked from the footer.
The privacy pitch (ADR-0083 §5/§7) is the product, so anything stricter than the above stays off this repo forever: no Google Analytics, no session replay, no fingerprinting, no script that sets third-party cookies. Swapping to a different privacy-respecting backend (Plausible, self-hosted Umami) is a drop-in replacement for the <Analytics /> component — keep the footer disclosure in sync if that happens.
Code organization
A few deliberate choices that come up often enough to be worth writing down (see #123):
pages/index.astrostays as one file. It's long (~940 lines) but the data for each section (features,providers,alternatives,faqs, …) is defined immediately above its markup in the same file. Extracting each section into its own*.astrocomponent would either fragment that data across multiple files or force a shared module just to pass props around — neither of which beats the "one file, one diff, one search" cost. The in-source{/* N. NAME */}comments are the table of contents.- Section anchor IDs live in
src/lib/anchors.ts. The header nav (Base.astro), the mobile-nav<details>disclosure (#119), the<section id="…">tags inindex.astro, and every cross-section<a href="#…">all import from that single constant. The audit-build script's anchor-integrity check (invariant #8) is a runtime backstop, not the primary defense — drift fails at type-check now. - No
_unused/graveyard. Components that no template imports get deleted in the same PR that orphans them (resolved in #128).git logis the archive — re-deriving a hand-tuned SVG from a past revision beats carrying it as dark matter that the next sweep has to re-evaluate. scripts/is grouped by when it runs.scripts/build/is chained bynpm run buildand gates the deploy;scripts/generate/is manual one-shots. Each file's header explains its exit-code contract and local-repro recipe — start there when CI is red.
Dev notes
- This repo should not import from
budi-cloudorbudi. Any shared assets (logo, brand colors) should live here and be copied to other repos rather than imported — these sites ship independently. - Visitor-facing copy does not link into
siropkin/budi's ADR tree ordocs/files. Contributors still reason in ADRs, but the public site reads as plain English: restate the behavior in visitor-friendly terms and keep it aligned with the current product. Docs drift is still the enemy — just solved by rewording the one surface that shipped, not by pointing marketing visitors at a decision record. - Images should be optimized (WebP/AVIF where possible) and served with cache-friendly headers.
- Keep the page JS-light. A marketing site that takes 4 seconds to render is not a good ad for a tool that brags about being fast.
Issue tracking
All budi repos use GitHub Issues as the issue tracker. File bugs, feature requests, and tasks as GitHub issues on the relevant repo.