Don't code? No problem! The easy install guide is available at bao.builders
BaoBuildBuddy is a career helper for game jobs.
- You tell it about yourself (resume, skills).
- It finds game-studio jobs and helps you apply.
- A local robot brain (Ollama) can chat and write letters.
- A local voice (Kokoro) can speak; a local ear (Whisper) can listen.
- A robot browser (Playwright) can click job sites for you.
- Everything is saved in a little notebook file (SQLite) on your computer.
No cloud TTS required. Cloud AI keys are optional — local-first is the default path.
BaoBuildBuddy is a full-stack toolkit for game-industry job seekers. It aggregates job listings, helps you build resumes and cover letters, runs AI-powered mock interviews, automates job applications with browser RPA, and tracks your progress with a gamification system.
graph TD
subgraph Client["Nuxt 4 Client :3001"]
UI[daisyUI 5 Components]
I18N[i18n: en/es/fr/ja]
PWA[SSR + Hydration]
end
subgraph Server["Elysia API :3000"]
Routes[REST Routes]
AI[AI Service]
RPA[RPA Runner]
Speech[Speech TTS/STT]
Gamify[Gamification XP]
end
subgraph Data["Data Layer"]
DB[(SQLite bun:sqlite)]
ORM[Drizzle ORM]
end
subgraph External["External Services"]
Ollama[Ollama Local LLM]
Kokoro[Kokoro TTS]
Whisper[Whisper STT]
Playwright[Playwright RPA]
JobBoards[Job Boards API]
end
UI --> Routes
Routes --> ORM
ORM --> DB
Routes --> AI
Routes --> RPA
Routes --> Speech
Routes --> Gamify
AI --> Ollama
Speech --> Kokoro
Speech --> Whisper
RPA --> Playwright
RPA --> JobBoards
Not sure where to start? Choose the guide that matches your goal:
| I want to... | Go here |
|---|---|
| Understand the app in plain English | ELI5 System Walkthrough |
| Get BaoBuildBuddy running for the first time | First-Time Setup Guide |
| Set up local AI with Ollama | Local AI Setup Guide |
| Learn the automation and RPA flows | Automation Guide |
| Run the full proof and verification pass | Verification Runbook |
| Deploy to Railway | Railway Deployment Guide |
| Publish the download site | Docs Site Deployment |
| Install a desktop app (no dev setup) | Non-Technical Install |
| Read the full technical reference | Keep reading this file |
| Resolve conflicting “audit” stack claims | Stack contract (Drizzle + Nuxt, not Prisma + htmx) |
| Browser visual / interactive UI proof | Verification Runbook + Page Verification Guide |
| SSOT defect / improvement ledgers | docs/ssot-ledger/ |
- Nuxt is the screen you click on.
- Elysia is the traffic controller.
- SQLite is the notebook that remembers things.
- Playwright is the robot browser.
- Bun is the runtime, package manager, bundler, and test runner.
- Tauri is the desktop shell.
- ELI5 — Explain Like I’m 5
- Pick Your Guide
- Quick Start
- Local AI Quick Path
- Non-Technical Install
- Architecture
- Configuration
- Running the App
- Automation & RPA
- Job Provider Registry
- AI Integration
- Additional Services
- Internationalization
- Database Schema
- Project Structure
- Client Pages & Features
- Validation & Quality Gates
- Verification Runbook
- Desktop Packaging (Tauri)
- Troubleshooting
- Final Checklist
- Documentation Index
| Required | Purpose |
|---|---|
| Bun (>=1.3.14) | Runtime, package manager, test runner |
| Git | Source control |
Optional: Rust + Cargo (for desktop builds), curl/jq (for diagnostics), at least one AI provider key.
Check your Bun version against the workspace manifest:
bun pm pkg get packageManager
# -> "bun@1.3.14"macOS / Linux:
git clone https://github.com/d4551/baobuildbuddy.git
cd baobuildbuddy
bash scripts/setup.shWindows (PowerShell):
git clone https://github.com/d4551/baobuildbuddy.git
cd baobuildbuddy
powershell -ExecutionPolicy Bypass -File scripts\setup.ps1The setup script installs dependencies, sets up Playwright Chromium, creates .env from .env.example, bootstraps the database, and runs validation checks.
| Flag | Bash | PowerShell | Effect |
|---|---|---|---|
| Skip verification | --skip-checks |
-SkipChecks |
Skip typecheck, lint, and test runs |
| Skip browser install | --skip-browser-install |
-SkipBrowserInstall |
Skip Playwright Chromium installation |
| Include build | --include-build |
-IncludeBuild |
Run bun run build after setup |
| Include desktop build | --include-desktop-build |
-IncludeDesktopBuild |
Run Tauri desktop build after setup |
| Help | --help |
-Help |
Print usage and exit |
git clone https://github.com/d4551/baobuildbuddy.git
cd baobuildbuddy
bun install
bun run automation:browsers:install
cp .env.example .env # Windows: copy .env.example .env
bun run db:generate
bun run db:pushbun run devThis starts the API server (port 3000) and Nuxt client (port 3001) together via scripts/dev-stack.ts. The stack defaults the Nuxt host to IPv4 loopback (127.0.0.1) so Playwright and other tools that dial 127.0.0.1:3001 are not blocked by an IPv6-only localhost bind.
Verify everything is working:
curl -fsS http://127.0.0.1:3000/api/health
curl -fsS http://127.0.0.1:3000/api/auth/status
curl -fsS "http://127.0.0.1:3000/api/jobs?limit=1" | headThen open http://127.0.0.1:3001 (or http://localhost:3001 when your OS resolves localhost to the same stack) in your browser.
Want BaoBuildBuddy to use AI on your own computer without cloud API keys?
- Install Ollama from ollama.com/download.
- Read the official Ollama Quickstart if you want the vendor walkthrough.
- Download a first model:
ollama pull llama3.2 - Open BaoBuildBuddy and go to Settings > AI Providers.
- Set the local endpoint to
http://localhost:11434/v1and leave the model blank for auto-detect. - Use the built-in test button before saving. The settings and setup flows now surface concrete local diagnostics such as unreachable endpoint, timeout, empty model list, and invalid selected model.
For the full beginner walkthrough, see docs/LOCAL_AI_SETUP.md.
For the current step-by-step proof flow covering:
- format, lint, test, and build gates
- SSR route HTML checks (
verify:pages) - multi-viewport browser smoke (
proof:browser-smoke) and interactive burndown (proof:browser-burndown) - full routed page screenshots with a report (
proof:pages) - export verification for resume / cover letter / portfolio
- desktop runtime verification
- Tauri 2 host-matching release generation plus staged multi-platform verification
use docs/VERIFICATION_RUNBOOK.md and the route checklist in docs/PAGE_VERIFICATION_GUIDE.md.
Grab a packaged desktop installer and skip the developer setup entirely. The latest verified builds are published at https://bao.builders/releases/ and mirrored in packages/desktop/releases in this repo.
A wild installer appeared. Choose your OS:
| Operating System | Download (v0.1.0) |
|---|---|
| macOS (Apple Silicon) | BaoBuildBuddy_0.1.0_aarch64.dmg |
| Windows (x64) | x64-setup.exe or x64-portable.zip |
| Linux (x64) | amd64.deb, x86_64.rpm, or amd64.AppImage |
| Linux (ARM64) | arm64.deb or aarch64.rpm |
Windows builds are 64-bit only. Verify downloads against sha256.txt; see packages/desktop/releases/README.md for the full artifact catalog and provenance.
_____
| |
| GLa | "The cake is a lie."
| D | But the architecture diagram is real.
| 0S |
|_____|
Browser ──> Nuxt SSR ──> Elysia API ──> SQLite
| | |
|── WebSocket ──> /ws/chat |── AI providers (5 adapters)
|── WebSocket ──> /ws/interview |── RPA subprocess (Bun.spawn)
|── WebSocket ──> /ws/automation |
|
Job provider registry
(ATS + gaming boards + company boards)
flowchart LR
subgraph Client["@bao/client"]
UI["Nuxt pages · components"]
Eden["Eden treaty client"]
WS["WebSocket clients"]
end
subgraph Server["@bao/server"]
API["Typed route modules"]
WSHandlers["ws/chat · interview · automation"]
Svcs["Services layer"]
end
subgraph Data["Data & external"]
DB[("SQLite")]
AI["AI providers"]
Jobs["Job providers"]
RPA["Scraper/RPA"]
end
UI --> Eden
UI --> WS
Eden -->|HTTP /api/*| API
WS --> WSHandlers
API --> Svcs
Svcs --> DB
Svcs --> AI
Svcs --> Jobs
Svcs --> RPA
RPA -->|Bun.spawn| Scraper["@bao/scraper"]
flowchart TB
subgraph UI["Nuxt UI"]
Chat["AI Chat"]
Voice["Test speaker / Mic"]
Export["Export PDF"]
end
subgraph API["Elysia /api"]
AIChat["/api/ai/chat"]
Synth["/api/speech/synthesize"]
Transcribe["/api/speech/transcribe"]
PDF["resume/cover/portfolio export"]
end
subgraph Local["On-machine services"]
Ollama["Ollama :11434/v1"]
Kokoro["Kokoro ONNX :8880/v1"]
Whisper["Whisper :8090/v1"]
SQLite[("SQLite ~/.bao/bao.db")]
end
Chat --> AIChat --> Ollama
Voice -->|local TTS| Synth --> Kokoro
Voice -->|local STT| Transcribe --> Whisper
Export --> PDF --> SQLite
AIChat --> SQLite
Provisioning the speech services. Both servers are Python and neither ships
its dependencies or model weights, so speech:whisper:serve and
speech:kokoro:serve exit immediately until you install them once:
# Whisper STT (:8090) — real faster-whisper inference
pip install faster-whisper fastapi uvicorn python-multipart
bun run speech:whisper:serve # downloads the `tiny` model on first run
# Kokoro TTS (:8880) — on-device ONNX, weights are not bundled
pip install kokoro-onnx
mkdir -p ~/.bao/kokoro && cd ~/.bao/kokoro
curl -LO https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files-v1.0/kokoro-v1.0.onnx
curl -LO https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files-v1.0/voices-v1.0.bin
bun run speech:kokoro:serve # 54 voices, af_heart by defaultOverride the model/voice locations with KOKORO_MODEL_PATH / KOKORO_VOICES_PATH
and the Whisper size with WHISPER_MODEL. The headed proofs additionally need an
X server and an encoder — xvfb-run (or DISPLAY=:99 with Xvfb) plus ffmpeg.
Fail-closed proofs (no speechSynthesis-only greens):
| Proof | What it proves |
|---|---|
proof:kokoro-tts |
RIFF WAV from Kokoro via UI + API |
proof:whisper-stt |
Local Whisper transcription via API |
proof:ollama-live |
Live nonce chat (not stub) |
proof:pdf-live |
Real %PDF- bytes from UI Export |
validate:zero-capability-debt |
Ledger Remaining empty + wiring markers |
validate:no-soft-test-skips |
No test.skip / hardcoded STT BLOCKED theater |
External or legacy agent prompts sometimes mention Prisma, htmx, or files that are not in this repo. Treat this README, AGENTS.md, and docs/feature-trace-matrix.md as canonical: the UI is Nuxt 4 + Vue + daisyUI with Eden Treaty to an Elysia API, persistence via Drizzle ORM and SQLite (bun:sqlite), not htmx-driven partial HTML or Prisma.
| Package | Path | What it does |
|---|---|---|
@bao/server |
packages/server |
Bun + Elysia API, Drizzle ORM, WebSockets, orchestration |
@bao/client |
packages/client |
Nuxt 4 SSR frontend, Tailwind CSS v4, daisyUI v5 |
@bao/shared |
packages/shared |
Shared types, contracts, constants, schemas, validation |
@bao/scraper |
packages/scraper |
Bun + Playwright automation executables |
@bao/desktop |
packages/desktop |
Tauri desktop packaging and release staging |
flowchart TD
Browser["Browser"] --> Client["packages/client · Nuxt SSR"]
Client --> Pages["pages · layouts · components"]
Client --> Composables["typed composables · api-normalizers"]
Client --> EdenClient["plugins/eden"]
Client --> FlowEngine["flow-engine · ui-layout"]
ServerTypes["packages/server/dist-types"]
Shared["packages/shared contracts"]
EdenClient -->|"typed HTTP calls"| ApiPrefix["/api"]
EdenClient -->|"type import"| ServerTypes
ApiPrefix --> App["packages/server/src/app"]
App --> Middleware["cors · swagger · rate-limit · logger · errorHandler · authGuard"]
App --> Routes["Typed route modules from route-modules.ts"]
App --> WebSockets["ws: chat · interview · automation"]
App --> Shared
App --> ServerTypes
Routes --> AuthRoutes["auth · user · settings"]
Routes --> CareerRoutes["jobs · resume · cover-letter · portfolio · interview · studios"]
Routes --> AutomationRoutes["automation · scraper · automation-screenshots"]
Routes --> PlatformRoutes["ai · gamification · skill-mapping · search · stats"]
CareerRoutes --> JobsSvc["jobs service"]
JobsSvc --> JobAggregator["job-aggregator"]
JobAggregator --> ProviderRegistry["provider-registry"]
ProviderRegistry --> ATSProviders["greenhouse · lever · company-board"]
ProviderRegistry --> GamingProviders["gaming-providers"]
JobsSvc --> MatchingSvc["matching-service"]
JobsSvc --> DedupSvc["deduplication"]
CareerRoutes --> DomainServices["resume · cover-letter · portfolio · interview · studio services"]
PlatformRoutes --> PlatformServices["ai · gamification · skill-mapping · search · statistics"]
PlatformServices --> SkillExtractor["skill-extractor"]
PlatformServices --> AiProviders["local · openai · gemini · claude · huggingface"]
AiProviders --> ExternalAI["provider APIs and local model endpoint"]
AutomationRoutes --> AutomationSvc["application-automation-service"]
AutomationRoutes --> ScraperSvc["scraper-service"]
AutomationSvc --> Runner["automation/rpa-runner"]
AutomationSvc --> SmartFieldMapper["smart-field-mapper"]
AutomationSvc --> EmailDelivery["email-delivery-service"]
ScraperSvc --> Runner
Runner --> ScraperPkg["packages/scraper"]
ScraperPkg --> Scripts["src/scripts"]
Scripts --> Runtime["Playwright runtime · ATS adapters · provider extractors"]
JobsSvc --> DB[("SQLite via Bun SQLite and Drizzle")]
DomainServices --> DB
PlatformServices --> DB
AutomationSvc --> AutomationRuns["automation_runs"]
ScraperSvc --> JobsStudios["jobs · studios ingestion"]
AutomationRuns --> DB
JobsStudios --> DB
Each Elysia route module owns its service directly -- routes call services, services call the database or external providers. Typed contracts in packages/shared are the source of truth for request/response shapes. Bun automation executables run in isolated subprocesses with JSON/NDJSON over stdin/stdout.
Key conventions:
- Global flow decisions live in
packages/client/constants/flow-engine.tsand are consumed viauseFlowEngine.ts. - Layout is tokenized in
packages/client/constants/ui-layout.tsand rendered throughPageScaffold,PageHeaderBlock,SectionGrid, andAppModalFrame. - AI provider display uses locale-driven keys (
aiProviderCatalog.*) andAIProviderIcon-- no hardcoded provider labels. - Interview role selection is adaptive: profile role, readiness rankings, pathway scores, and live job signals drive recommendations.
- Skills readiness uses typed IDs (
feedbackId,improvementSuggestions,nextSteps) so all UI copy is locale-driven.
.---------.
| .-------. | ~~~ OPTIONS MENU ~~~
| | .env | |
| | | | Every configurable value lives
| '-------' | in .env or a source-of-truth
'----( )----' config file. Nothing is hardcoded.
| |
| | "Hey! Listen!" -- set your
'-' LOCAL_MODEL_ENDPOINT first.
| Key | Purpose | Details |
|---|---|---|
PORT |
API bind port | Validated in range 1..65535 |
HOST |
API bind host | Passed to Elysia listener |
DB_PATH |
SQLite database file location | Parent directory must be writable |
LOG_LEVEL |
Logging verbosity | info, debug, warn, error |
CORS_ORIGINS |
Comma-separated allowed origins | Defaults include localhost |
BAO_DISABLE_AUTH |
Disable auth explicitly | Set true or 1 for local-only dev |
BAO_AUTH_SETUP_TOKEN |
First-run API key bootstrap token | Required only when auth stays enabled during setup |
| Key | Purpose |
|---|---|
NUXT_PUBLIC_API_BASE |
API base URL for useFetch/$fetch |
NUXT_PUBLIC_WS_BASE |
WebSocket base URL |
NUXT_PUBLIC_API_PROXY |
Dev proxy target (defaults to http://localhost:${PORT}) |
NUXT_PUBLIC_QUERY_STALE_TIME_MS |
TanStack Query stale time |
NUXT_PUBLIC_QUERY_RETRY_COUNT |
TanStack Query retry budget |
NUXT_PUBLIC_QUERY_REFETCH_ON_FOCUS |
Refetch on window focus |
NUXT_PUBLIC_I18N_DEFAULT_LOCALE |
Initial locale (en-US default) |
NUXT_PUBLIC_I18N_FALLBACK_LOCALE |
Fallback when translations are missing |
NUXT_PUBLIC_I18N_LOCALE_COOKIE_KEY |
Cookie key for persisted locale |
Do NOT set
NUXT_PUBLIC_I18N_SUPPORTED_LOCALESin.env. Nuxt runtime config replaces the parsed array with a raw string, which breaks the i18n plugin. The default is handled innuxt.config.ts.
At least one provider is needed for AI features (chat, interviews, email drafts, resume review, cover letters). Keys can also be set via Settings > AI Providers in the UI.
| Key | Purpose |
|---|---|
LOCAL_MODEL_ENDPOINT |
Local inference server URL |
LOCAL_MODEL_NAME |
Local model identifier |
OPENAI_API_KEY |
OpenAI cloud provider |
GEMINI_API_KEY |
Google Gemini cloud provider |
CLAUDE_API_KEY |
Anthropic Claude cloud provider |
HUGGINGFACE_TOKEN |
HuggingFace Inference API (free tier requires a token) |
| Key | Purpose | Default |
|---|---|---|
AUTOMATION_STDIO_BUFFER_LIMIT |
Max stdout lines from scraper scripts | 200 (increase to 2000 for large outputs) |
AUTOMATION_SCRIPT_TIMEOUT_MS |
Max execution time per script | 30000 |
Install bundled Chromium: bun run automation:browsers:install
Provider configuration for job ingestion is stored in settings.automationSettings.jobProviders. Populate it via PUT /api/settings before running ingestion. Required keys include providerTimeoutMs, greenhouseBoards[], leverCompanies[], companyBoards[], gamingPortals[], and related defaults. See the settings schema for the full shape.
| File | Governs |
|---|---|
packages/server/src/config/env.ts |
Server environment validation |
packages/server/src/config/paths.ts |
File system paths used by server |
packages/client/nuxt.config.ts |
Client runtime config, proxy, modules |
packages/scraper/package.json |
Bun automation runtime dependencies |
.env.example |
Template for all env vars |
_____________
| ___ ___ |
| | 1 || 2 | | PLAYER SELECT
| |___||___| |
| ___ ___ | 1 = Full stack (bun run dev / scripts/dev-stack.ts)
| | 3 || 4 | | 2 = Server only (bun run dev:server)
| |___||___| | 3 = Client only (bun run dev:client)
|_____________| 4 = Split terminals
"Press START to begin"
bun run devStarts server + client via scripts/dev-stack.ts:
- API server on
PORT(default 3000), bound for local access - Nuxt client on port 3001, host default
127.0.0.1(IPv4 loopback; see Troubleshooting if Playwright cannot reach the UI)
Terminal 1:
bun run dev:serverTerminal 2:
bun run dev:clientWhen starting the client alone, prefer an explicit IPv4 host if you will drive the UI with Playwright:
bun run --cwd packages/client dev -- --host 127.0.0.1 --port 3001| Endpoint | Default URL | Config key |
|---|---|---|
| API server | http://127.0.0.1:3000 |
PORT |
| Client UI | http://127.0.0.1:3001 |
dev-stack / Nuxt --host |
| Chat WebSocket | ws://127.0.0.1:3000/api/ws/chat |
NUXT_PUBLIC_WS_BASE |
| Interview WebSocket | ws://127.0.0.1:3000/api/ws/interview |
NUXT_PUBLIC_WS_BASE |
| Automation WebSocket | ws://127.0.0.1:3000/api/ws/automation |
NUXT_PUBLIC_WS_BASE |
| Script | Command | Purpose |
|---|---|---|
| Dev (full) | bun run dev |
Start server + client via scripts/dev-stack.ts |
| Dev (stack) | bun run dev:stack |
Alias to scripts/dev-stack.ts |
| Dev server | bun run dev:server |
Start API server only |
| Dev client | bun run dev:client |
Start Nuxt client only |
| Dev desktop | bun run dev:desktop |
Start Tauri desktop shell |
| Build | bun run build |
Build server and client |
| Build desktop | bun run build:desktop |
Build Tauri installer for current host; merges into packages/desktop/releases without deleting other OS artifacts |
| Typecheck | bun run typecheck |
TypeScript checking across all packages |
| Test | bun run test |
Run all test suites |
| Lint | bun run lint |
All validators + Biome + ESLint + typecheck |
| Lint fix | bun run lint:fix |
Autofix with guardrails preserved |
| Format | bun run format |
Apply Biome formatter |
| Format check | bun run format:check |
Verify formatter output |
| DB generate | bun run db:generate |
Generate Drizzle migration files |
| DB push | bun run db:push |
Push schema changes to SQLite |
| DB studio | bun run db:studio |
Open Drizzle Studio GUI |
| Release desktop (macOS) | bun run release:desktop:macos |
Native macOS release artifacts |
| Release desktop (Windows) | bun run release:desktop:windows |
Native Windows release artifacts |
| Release desktop (Linux x64) | bun run release:desktop:linux-x64 |
Native Linux x64 release artifacts |
| Release desktop (Linux ARM) | bun run release:desktop:linux-arm64 |
Native Linux ARM64 release artifacts |
| Release refresh (all staged OSes) | bun run release:refresh:all-os |
Assemble staged release artifacts + checksums |
| Docs site bundle | bun run docs-site:bundle |
Stage the deployable site into dist/docs-site |
| Docs site deploy | bun run docs-site:deploy |
Publish dist/docs-site over FTPS (env credentials) |
| Verify pages | bun run verify:pages |
Validate SSR routes return proper HTML |
| Browser visual smoke | bun run proof:browser-smoke |
Playwright screenshots @ mobile/tablet/desktop |
| Browser interaction burndown | bun run proof:browser-burndown |
Click/fill probe + 5Q ledger per route × viewport |
| Page screenshot proof | bun run proof:pages |
Full routed-page PNG bundle + report |
| Validate stack pins | bun run validate:stack-versions |
Assert installed Elysia/Eden/etc. match workspace pins |
| Server type contract | bun run --cwd packages/server build:types |
Generate dist-types for client typecheck |
| Validate ARIA | bun run validate:aria |
Interactive labeling + dialog semantics |
| Validate layout tokens | bun run validate:ui-layout-tokens |
Block hardcoded width/grid literals |
| Validate UI | bun run validate:ui |
WCAG contrast + hardcoded color checks |
| Validate page SEO | bun run validate:page-seo |
Require SSR useServerSeoMeta on core pages |
| Validate i18n | bun run validate:i18n-ui |
Reject static template copy / missing t() keys |
| Validate no try/catch | bun run validate:no-try-catch |
Enforce no-try/catch policy |
| ASCII validation | bun run scripts/validate-ascii-geometry.ts README.md |
Verify ASCII-art geometry |
| Audit official LLM docs | bun run audit:official-llms |
Check Elysia/Nuxt/Bun/daisyUI llms.txt reachability |
___
| | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| ! | CAUTION: Entering RPA territory
|___|
/ \ Automation runs are persisted to
/ BAO \ automation_runs in SQLite for full
/_______\ audit trail and replay capability.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
"Do a barrel roll!" -- but only after
Playwright Chromium is installed.
Automation execution flows from automationRoutes through application-automation-service.ts to rpa-runner.ts.
- API route receives a typed job-apply payload.
- Service resolves required entities from the database (resume, optional cover letter).
- A new
automation_runsrecord is created with a unique run ID. rpa-runner.tsspawns a Bun subprocess withBun.spawn.- The request payload goes to the script on
stdin. - The Playwright script navigates, fills forms, clicks, and captures screenshots.
- NDJSON/JSON protocol output is parsed and persisted.
- The run status is updated (
successorerror).
| Script ID | File | Purpose |
|---|---|---|
job-apply |
packages/scraper/src/scripts/job-apply.ts |
Job application form automation |
scraper-hitmarker |
packages/scraper/src/scripts/scraper-hitmarker.ts |
Scrape Hitmarker jobs |
scraper-grackle |
packages/scraper/src/scripts/scraper-grackle.ts |
Scrape GrackleHQ jobs |
scraper-workwithindies |
packages/scraper/src/scripts/scraper-workwithindies.ts |
Scrape Work With Indies |
scraper-remotegamejobs |
packages/scraper/src/scripts/scraper-remotegamejobs.ts |
Scrape RemoteGameJobs |
scraper-gamesjobsdirect |
packages/scraper/src/scripts/scraper-gamesjobsdirect.ts |
Scrape GamesJobsDirect |
scraper-pocketgamer |
packages/scraper/src/scripts/scraper-pocketgamer.ts |
Scrape PocketGamer.biz |
studio-scraper |
packages/scraper/src/scripts/studio-scraper.ts |
Curated studio directory |
Scripts read JSON from stdin, produce protocol-compliant JSON/NDJSON on stdout, and exit non-zero on hard failure.
Input:
{
"jobUrl": "https://example.com/job/application",
"resume": {
"personalInfo": {
"fullName": "Player One",
"email": "player@example.com",
"phone": "+1 555 0100",
"location": "Remote"
},
"education": ["..."],
"experience": ["..."],
"skills": ["..."]
},
"coverLetter": {
"company": "Acme",
"position": "Senior Game Designer",
"content": {}
},
"customAnswers": {
"q_salary": "120000",
"q_relocation": "No"
}
}Success:
{
"success": true,
"error": null,
"screenshots": ["step-01.png", "step-02.png"],
"steps": [
{ "action": "navigate", "status": "ok" },
{ "action": "fill_full_name", "status": "ok" },
{ "action": "submit", "status": "ok" }
]
}Failure:
{
"success": false,
"error": "No matching submit button",
"screenshots": [],
"steps": [{ "action": "click_submit", "status": "error" }]
}Screenshots are served through GET /api/automation/screenshots/:runId/:index.
rpa-runner.ts calls Bun.spawn with stdin: "pipe", stdout: "pipe", stderr: "pipe". It passes the payload to stdin, reads both streams, and returns structured context on non-zero exit.
All automation types support scheduling through one persisted model:
POST /api/automation/job-apply/schedulePOST /api/automation/email-response/schedulePOST /api/automation/scrape/schedule
Each writes a pending row to automation_runs with the requested time at input.schedule.runAt, then queues an in-memory timer. On restart, pending rows are reloaded and timers restored. No separate cron table.
AUTOMATION_RUNTIME_ERRORAUTOMATION_TIMEOUTAUTOMATION_CANCELLEDSCRIPT_PROTOCOL_ERRORSCRIPT_OUTPUT_INVALIDOUTPUT_PERSISTENCE_ERROROUTPUT_VALIDATION_ERROR
.-----------.
/ JOBS BOARD \ "War. War never changes."
| +-----------+ | But job boards do. The provider
| | Greenhouse| | registry normalizes provider behavior
| | Lever | | so the aggregator doesn't have to
| | Company | | care which ATS you're scraping.
| +-----------+ |
\ /
'------------'
The job system lives in packages/server/src/services/jobs/:
| File | What it does |
|---|---|
job-aggregator.ts |
Orchestrates fetching across all providers |
matching-service.ts |
Scores jobs against user profile and skills |
deduplication.ts |
Deduplicates listings from multiple boards |
providers/provider-interface.ts |
Common interface all providers implement |
providers/provider-registry.ts |
Add/remove providers at runtime |
providers/greenhouse.ts |
Greenhouse ATS integration |
providers/lever.ts |
Lever ATS integration |
providers/company-board.ts |
Direct company career page scraping |
providers/provider-settings.ts |
Settings-backed provider configuration |
providers/gaming-providers.ts |
Game-industry board aggregation |
The default set includes Greenhouse, Lever, Hitmarker, GrackleHQ, Work With Indies, RemoteGameJobs, GamesJobsDirect, PocketGamer.biz, plus configured SmartRecruiters/Workday/Ashby company boards.
The aggregator calls each provider, deduplicates results, scores them against your profile, and persists everything to SQLite.
.-------------.
/ CHOOSE \
/ YOUR CLASS \
/ \
| [1] Local Mage |
| [2] OpenAI Knight |
| [3] Gemini Ranger | "Would you kindly"
| [4] Claude Healer | configure at least
| [5] HF Summoner | one provider?
\ /
\_________________/
The AI subsystem is in packages/server/src/services/ai/:
| File | What it does |
|---|---|
ai-service.ts |
Routes requests to the active provider |
provider-interface.ts |
Common interface for all providers |
local-provider.ts |
Connects to Ollama, LM Studio, etc. |
openai-provider.ts |
OpenAI API integration |
gemini-provider.ts |
Google Gemini API integration |
claude-provider.ts |
Anthropic Claude API integration |
huggingface-provider.ts |
HuggingFace Inference API integration |
context-manager.ts |
Manages conversation history and context windows |
prompts.ts |
Prompt templates for resume review, interviews, cover letters |
- The settings row persists a canonical
aiRoutingmap keyed by purpose:chat,interviewQuestions,interviewFeedback,resume,coverLetter,emailResponse,jobMatch,scrapeEnrichment, andautomationFieldMapping. - Each AI route/service call now passes an explicit purpose so provider and model selection happens server-side per workflow instead of through one global preference.
- Local inference is centered on
http://localhost:11434/v1and supports blank-model auto-detect; diagnostics report unreachable endpoints, timeouts, empty/modelsresponses, and invalid configured models. - The context manager still owns conversation state and prompt construction for chat surfaces.
All AI calls are server-owned. The client communicates through API routes and WebSocket endpoints, never directly to providers.
- Edit hand-maintained source, docs, schemas, and config under
packages/*,docs/, andscripts/. - Treat
.nuxt,.output,dist-types, desktop release payloads, and other packaged/generated artifacts as derived outputs. - Regenerate derived outputs after source changes; do not hand-edit them.
____________________________
| SERVICE INVENTORY |
|____________________________|
| |
| "I used to be an |
| adventurer like you, |
| then I took a service |
| layer to the knee." |
|____________________________|
| Service | File | Purpose |
|---|---|---|
| CV Questionnaire | cv-questionnaire-service.ts |
Guided questionnaire flow for building resume data |
| Data Service | data-service.ts |
Shared data access patterns |
| Export Service | export-service.ts |
Export resumes, portfolios, cover letters to PDF/JSON |
| Email Delivery | email-delivery-service.ts |
SMTP delivery for automation email responses |
| Skill Extractor | skill-extractor.ts |
Extract and normalize skills from listings/resumes |
| Skill Mapping | skill-mapping-service.ts |
Map user skills to job requirements for scoring |
| Smart Field Mapper | automation/smart-field-mapper.ts |
AI-assisted form field mapping for job-apply RPA |
BaoBuildBuddy ships these locale packs:
| Locale | File |
|---|---|
en-US |
packages/client/locales/en-US.ts |
es-ES |
packages/client/locales/es-ES.ts |
fr-FR |
packages/client/locales/fr-FR.ts |
ja-JP |
packages/client/locales/ja-JP.ts |
Source of truth: packages/client/plugins/i18n.ts registers catalogs. nuxt.config.ts defines i18n runtime config. Locale files follow the schema in en-US.ts.
Resolution order: saved cookie locale -> Accept-Language header (q-weighted) -> browser locale -> configured default.
To add a language:
- Add a catalog file under
packages/client/locales. - Register it in
I18N_MESSAGE_CATALOG. - Add the locale to
NUXT_PUBLIC_I18N_SUPPORTED_LOCALES. - Add matching preference/voice mappings where needed.
.-----------.
/ \ "A man chooses. A slave obeys."
| 16 TABLES | But a schema migrates.
| IN SQLite |
\ / All tables are defined in
'-----------' packages/server/src/db/schema/
| Schema File | Tables | Purpose |
|---|---|---|
user.ts |
user_profile | User accounts and profiles |
auth.ts |
auth | Authentication sessions and tokens |
resumes.ts |
resumes | Resume data with structured sections |
cover-letters.ts |
cover_letters | Generated and custom cover letters |
portfolios.ts |
portfolios, portfolio_projects | Portfolio collections and projects |
interviews.ts |
interview_sessions | Mock interview sessions and transcripts |
studios.ts |
studios | Game studio directory |
jobs.ts |
jobs, saved_jobs, applications | Aggregated job listings, saves, applications |
skill-mappings.ts |
skill_mappings | User skill profiles and gap analysis |
gamification.ts |
gamification | XP, level, achievements, streaks |
settings.ts |
settings | User preferences and app configuration |
automation-runs.ts |
automation_runs | RPA execution audit trail |
chat-history.ts |
chat_messages | AI conversation history |
Migrations live in packages/server/src/db/migrations/. Seed data in packages/server/src/db/seed/ provides initial studio records and industry reference data.
"The right man in the wrong place
can make all the difference in the world."
-- But the right file in the wrong directory? Not so much.
baobuildbuddy/
+-- packages/
| +-- server/ Bun + Elysia API server
| | +-- src/
| | | +-- routes/ API route modules with route-level tests
| | | | +-- auth.routes.ts
| | | | +-- user.routes.ts
| | | | +-- settings.routes.ts
| | | | +-- jobs.routes.ts
| | | | +-- resume.routes.ts
| | | | +-- cover-letter.routes.ts
| | | | +-- portfolio.routes.ts
| | | | +-- interview.routes.ts
| | | | +-- studio.routes.ts
| | | | +-- scraper.routes.ts
| | | | +-- ai.routes.ts
| | | | +-- gamification.routes.ts
| | | | +-- skill-mapping.routes.ts
| | | | +-- search.routes.ts
| | | | +-- stats.routes.ts
| | | | +-- automation.routes.ts
| | | | +-- automation-screenshots.routes.ts
| | | +-- services/ Business logic layer
| | | | +-- ai/ 5 provider adapters + context manager + prompts
| | | | +-- automation/ application-automation-service.ts, rpa-runner.ts
| | | | +-- jobs/ Aggregator, matching, dedup, provider registry
| | | | | +-- providers/ greenhouse, lever, company-board, gaming-providers
| | | | +-- resume-service.ts
| | | | +-- cover-letter-service.ts
| | | | +-- portfolio-service.ts
| | | | +-- interview-service.ts
| | | | +-- gamification-service.ts
| | | | +-- scraper-service.ts
| | | | +-- search-service.ts
| | | | +-- statistics-service.ts
| | | | +-- export-service.ts
| | | | +-- data-service.ts
| | | | +-- cv-questionnaire-service.ts
| | | | +-- skill-extractor.ts
| | | | +-- skill-mapping-service.ts
| | | +-- db/
| | | | +-- schema/ Drizzle table modules + schema-modules.ts
| | | | | +-- user.ts, auth.ts, resumes.ts, cover-letters.ts
| | | | | +-- portfolios.ts, interviews.ts, studios.ts, jobs.ts
| | | | | +-- skill-mappings.ts, gamification.ts, settings.ts
| | | | | +-- automation-runs.ts, chat-history.ts
| | | | +-- migrations/
| | | | +-- seed/ Initial gaming data and studio records
| | | | +-- client.ts, init.ts
| | | +-- middleware/ auth.ts, error-handler.ts, logger.ts
| | | +-- ws/ chat.ws.ts, interview.ws.ts, automation.ws.ts
| | | +-- config/ env.ts (validation), paths.ts
| +-- client/ Nuxt 4 SSR application
| | +-- pages/ SSR routes for setup, jobs, studios, interview, AI, automation, docs
| | +-- components/ Shared UI and feature Vue components
| | | +-- ai/ AIChatBubble, AIStreamingResponse, BaoFairy
| | | +-- resume/ ResumePreview, ExperienceList, PersonalInfoForm,
| | | | SkillsEditor, EducationList
| | | +-- jobs/ JobCard, JobMatchScore, JobSearchBar
| | | +-- interview/ InterviewChat, ScoreCard, StudioSelector
| | | +-- gamification/ DailyChallenge, XPBar, AchievementBadge
| | | +-- portfolio/ PortfolioGrid, ProjectCard
| | | +-- layout/ AppNavbar, AppSidebar, AppDock
| | | +-- ui/ ConfirmDialog, LoadingSkeleton
| | +-- composables/ Typed data, websocket, speech, and view-state composables
| | | +-- useApi, useAuth, useUser, useSettings, useSettingsQuery
| | | +-- useTheme, useWebSocket, useSpeech, useTTS, useSTT
| | | +-- useJobs, useSearch, useResume, useCoverLetter
| | | +-- usePortfolio, useStudio, useInterview, useAI
| | | +-- useAutomation, useGamification, useSkillMapping, useStatistics
| | +-- plugins/ vue-query.ts, toast.client.ts, eden.ts
| | +-- middleware/ auth.ts (client-side auth guard)
| | +-- layouts/ default.vue, auth-shell.vue
| | +-- utils/ errors.ts
| | +-- types/ nuxt.d.ts, speech.d.ts
| | +-- assets/css/ main.css
| +-- shared/ Cross-package contracts
| | +-- src/
| | | +-- types/ Shared domain types
| | | | +-- user, ai, resume, interview, jobs, cover-letter
| | | | +-- portfolio, studio, gamification, skill-mapping
| | | | +-- settings, search
| | | +-- schemas/ Shared validation and protocol schemas
| | | | +-- user.schema, resume.schema, job.schema
| | | | +-- interview.schema, settings.schema
| | | | +-- portfolio.schema, skill-mapping.schema
| | | +-- constants/ Runtime, API, automation, AI, and UI contract constants
| | | +-- utils/ Shared parsing, validation, and formatting helpers
| | | +-- constants/, schemas/, types/, utils/ Direct subpath export surface
| +-- scraper/ Bun automation runtime
| +-- src/scripts/ Bun/TS automation entrypoints
| +-- src/providers/ Playwright scraper extractors
| +-- src/job-apply/ ATS integration runtime
| +-- src/runtime/ IO/protocol/browser helpers
| +-- package.json
+-- scripts/
| +-- setup.sh Automated setup for macOS / Linux
| +-- setup.ps1 Automated setup for Windows (PowerShell)
| +-- validate-ascii-geometry.ts ASCII art geometry checker
| +-- validate-no-try-catch.ts Repository no-try/catch policy validator
| +-- validate-ui-accessibility.ts WCAG + hardcoded-color drift validator
+-- docs/
| +-- STACK-CONTRACT.md Binding stack + UI SSOT contract
| +-- ELI5_SYSTEM_WALKTHROUGH.md Plain-English system overview
| +-- STARTER_GUIDE.md First-time setup guide
| +-- LOCAL_AI_SETUP.md Local AI with Ollama
| +-- AUTOMATION.md Automation contracts and runtime
| +-- VERIFICATION_RUNBOOK.md Lint/test/build + browser + export proof
| +-- PAGE_VERIFICATION_GUIDE.md Route-by-route screenshot / 5Q checklist
| +-- feature-trace-matrix.md Route-to-service-to-UI traceability
| +-- ssot-ledger/ Defect / improvement / baseline ledgers
| +-- RAILWAY.md Railway deployment guide
+-- .env.example
+-- package.json
+-- drizzle.config.ts
+-- biome.json
_____________________
| _______________ |
| | | | "All your base
| | WORLD MAP | | are belong to us."
| | | |
| | CORE ROUTES | | Navigate the SSR app across
| | + FEATURES | | the main product surfaces.
| |_______________| |
|_____________________|
| Feature Area | Pages | Key Composables |
|---|---|---|
| Home, Setup & Docs | index, setup, settings, docs/api |
useAuth, useSettings, useTheme |
| Resume | resume/index, resume/build, resume/preview |
useResume |
| Cover Letter | cover-letter/index, cover-letter/[id] |
useCoverLetter |
| Portfolio | portfolio/index, portfolio/preview |
usePortfolio |
| Interview | interview/index, interview/session, interview/history |
useInterview, useWebSocket |
| AI Chat | ai/dashboard, ai/chat |
useAI, useChatVoice, useSpeech |
| Studios | studios/index, studios/[id], studios/analytics |
useStudio |
| Jobs | jobs/index, jobs/[id] |
useJobs, useSearch |
| Automation | automation/index, automation/job-apply, automation/email, automation/scraper, automation/runs, automation/runs/[id] |
useAutomation |
| Skills & XP | skills/index, skills/pathways, gamification |
useSkillMapping, useGamification |
- SSR-first data loading by default; composables for client-side interactivity.
useFetchfor page-level data,$fetchfor user-triggered actions.- Async state (
idle,pending,success,error) mapped to daisyUI components. - The Eden client (
plugins/eden.ts) provides end-to-end type safety between Nuxt and the API. - Dynamic endpoints use Eden function-param invocation (e.g.
api.resumes({ id }).get()). - API payloads are normalized in
composables/api-normalizers.tsbefore binding to domain state. - Error handling follows Elysia centralized
onErrormiddleware and Eden{ data, error }branching. - TanStack Vue Query (
plugins/vue-query.ts) manages cache, stale time, and retry.
______
| | ~~~ SAVE POINT ~~~
| SAVE |
|______| Before you open the UI, run these
/ \ verification checks. Every check that
/ () \ passes is XP earned. Every check
/________\ skipped is a Boo that haunts you later.
bun run format:check
bun run typecheck
bun run lint
bun run test
bun run buildbun run validate:page-seo
bun run validate:i18n-ui
bun run validate:aria
bun run validate:ui-layout-tokens
bun run validate:ui
bun run verify:pagesWith the stack running (bun run dev), run Playwright visual proof against IPv4 loopback (not curl-as-UI):
PAGE_PROOF_CLIENT_BASE=http://127.0.0.1:3001 bun run proof:browser-smoke
PAGE_PROOF_CLIENT_BASE=http://127.0.0.1:3001 bun run proof:browser-burndownproof:browser-smoke— viewport matrix mobile (320) → tablet (768) → desktop (1440); one screenshot per static app route; fails on console/page errors and shell regressions.proof:browser-burndown— same viewport order; clicks labeled controls, probes textboxes, writesfiveq-ledger.json+burndown-report.json.
Route-by-route human review checklist: docs/PAGE_VERIFICATION_GUIDE.md.
The UI validation pipeline enforces:
- WCAG AA color contrast for daisyUI theme pairs
- No hardcoded UI colors in client source
- No static user-facing copy or static ARIA/placeholder attributes in templates
- Core pages define SSR
useServerSeoMetawith localizedtitleanddescription - All
t('...')keys resolve in theen-USlocale schema - Form controls are programmatically labeled
- Clickable surfaces are keyboard-operable and focusable
- Modal surfaces use
AppModalFramewitharia-modalandaria-labelledby - Core pages use tokenized layout primitives (
PageScaffold/SectionGrid)
flowchart LR
Templates["Vue templates + shared UI primitives"] --> Alignment["bun run validate:alignment"]
Alignment --> UIValidate["validate:page-seo + validate:i18n-ui + validate:aria + validate:ui"]
UIValidate --> UIState{"Layout, daisyUI, and accessibility checks pass?"}
UIState -->|No| UIFixes["Fix theme token pairs or remove hardcoded colors"]
UIFixes --> Alignment
UIState -->|Yes| A11yLint["Client ESLint + vuejs-accessibility"]
A11yLint --> LintState{"A11y errors = 0?"}
LintState -->|No| Fixes["Fix labels, keyboard handlers, control semantics"]
Fixes --> A11yLint
LintState -->|Yes| BrowserQA["Manual browser QA: tab order, Enter/Space, CTA wiring"]
BrowserQA --> Ready["Release-ready UI"]
Context7 verification: bun run audit:official-llms fetches and validates llms.txt from official sources (Elysia, Nuxt, Bun, daisyUI). Each source is checked for required markers to ensure AI assistants receive up-to-date docs. When Context7 MCP quota is unavailable, use that script plus the official llms.txt URLs as the safe equivalent.
Verify stack alignment (binding pin check is validate:stack-versions, not npm view latest):
bun run ci:alignment
bun run validate:stack-versions
bun run audit:stack-versions # advisory: prints npm-latest vs workspace; does not gate CI alone
bun run verify:bun-baseline
bun run validate:alignmentFull local quality gate and desktop verification:
bun ci
bun run lint
bun run typecheck
bun run test
bun run build
bun run build:desktopPer-platform native release staging:
bun run release:desktop:macos -- --output-root .desktop-release-artifacts --release
bun run release:desktop:windows -- --output-root .desktop-release-artifacts --release
bun run release:desktop:linux-x64 -- --output-root .desktop-release-artifacts --release
bun run release:desktop:linux-arm64 -- --output-root .desktop-release-artifacts --releaseRelease truth:
bun run build:desktopregenerates only the current host desktop artifact.- Fresh native installer regeneration is host-specific. A macOS machine cannot honestly claim fresh Windows or Linux installer generation from a local run.
- To regenerate every native installer, run the matching
release:desktop:*command on each target host or through the CI matrix, then assemble the staged outputs.
Assemble multi-platform release set:
bun run release:refresh:all-os| Command | Expected result |
|---|---|
bun run lint |
No warnings or errors |
bun run typecheck |
No TypeScript diagnostics |
bun run test |
All test suites pass |
bun run build |
All packages build successfully |
bun run build:desktop |
Current-host desktop packaging succeeds |
bun run prepare:desktop-runtime |
Build + stage src-tauri/gen/runtime (server/bin/scraper; gitignored) |
bun run verify:desktop-runtime |
Packaged runtime executes deterministic automation proof |
bun run proof:dom-reactivity |
Headed CM6/theme/search/viewport reactivity; findings=0 |
bun run proof:on-device-speech |
On-device Web Speech STT+TTS (browser); findings=0 |
bun run speech:kokoro:serve |
Local Kokoro ONNX OpenAI-compatible TTS (:8880/v1) |
bun run proof:kokoro-tts |
Local Kokoro TTS UI+API RIFF WAV; findings=0 |
bun run validate:local-kokoro-tts |
Fail-closed Kokoro wiring (not speechSynthesis-only) |
bun run speech:whisper:serve |
Local Whisper STT OpenAI-compatible (:8090/v1) |
bun run proof:whisper-stt |
Local Whisper STT via /api/speech/transcribe; findings=0 |
bun run proof:editor-ide |
Vim/minimap/TipTap/Cmd+P IDE surfaces; findings=0 |
bun run validate:zero-capability-debt |
Forbids ledger Remaining theater + requires wiring |
bun run validate:openapi-descriptions |
Every route detail has description (no API Docs empty stubs) |
bun run verify:pages |
All SSR routes and content checks pass |
bun run proof:browser-smoke |
Multi-viewport screenshots; 0 capture failures |
bun run proof:browser-burndown |
Interactive matrix; 0 findings; 5Q ledger complete |
bun run validate:stack-versions |
Installed package versions match workspace pins |
bun run ci:alignment |
Frozen-lockfile install + alignment gate passes |
bun run validate:alignment |
Bun + daisyUI alignment passes |
| Endpoint | Purpose | Expected Response |
|---|---|---|
/api/health |
Readiness probe | JSON with status and database |
/api/auth/status |
Auth state | Whether auth system is initialized |
/api/jobs |
Job search | Paginated job list |
/api/studios |
Studio data | Studio list |
/api/resumes |
Resume CRUD | Resume list |
/api/cover-letters |
Cover letter CRUD | Cover letter list |
/api/portfolio |
Portfolio CRUD | Portfolio project list |
/api/interview/sessions |
Interview sessions | Interview history |
/api/skills/mappings |
Skill mapping CRUD | Mapped skills list |
/api/skills/pathways |
Career pathways | Ranked pathways by match score |
/api/skills/readiness |
Career readiness | Readiness score and breakdown |
/api/automation/job-apply |
Start job automation | RpaRunExecutionEnvelope (status running) |
/api/automation/email-response |
Generate email response | { runId, status, reply, provider, model } |
/api/automation/scrape |
Run scraper now | RpaRunExecutionEnvelope |
/api/automation/runs |
Automation audit | Persisted run records |
/api/automation/screenshots/:runId/:index |
Run screenshot | PNG/JPEG/WebP image stream |
/api/gamification/progress |
XP and level progression | Gamification progress payload |
/api/stats/dashboard |
Usage statistics | Aggregate stat payload |
/api/ws/chat |
AI chat | WebSocket upgrade handshake |
/api/ws/interview |
Mock interview | WebSocket upgrade handshake |
/api/ws/automation |
Automation progress events | WebSocket event stream |
______________________
/| |\ Desktop packaging uses a native shell and
/ | TAURI | \ no separate Electron runtime.
/__|_____________________|__\ It launches the existing Bun stack and opens
| | | | it in a desktop window.
|__| .-.\ /.-. |__| This keeps one codebase for web + desktop.
| | |o| |o| | |
| | '-' '-' | |
| |_____________________| |
|___________________________|
Tauri is the default desktop path because it reuses your existing Bun stack with a thin native shell and no extra runtime overhead.
- Rust toolchain (
rustup+cargo) - macOS/Linux: system C/C++ build tools
- Windows: Visual C++ Build Tools
rustc --version
cargo --versionbun run dev:desktopThis auto-starts the server/client stack if needed and opens the app at http://127.0.0.1:3001 in a Tauri window.
Single-host build (current platform):
bun run build:desktopMatching-host release builds (run each on its target platform):
bun run release:desktop:macos -- --output-root .desktop-release-artifacts --release
bun run release:desktop:windows -- --output-root .desktop-release-artifacts --release
bun run release:desktop:linux-x64 -- --output-root .desktop-release-artifacts --release
bun run release:desktop:linux-arm64 -- --output-root .desktop-release-artifacts --releaseDo not describe that as "everything regenerated" unless each target command actually ran on its matching host or in CI on that target platform.
Optional installable variants (defaults are on; set false to omit):
DESKTOP_RELEASE_MACOS_ARCHITECTURES=aarch64,x86_64,universal bun run release:desktop:macos -- --output-root .desktop-release-artifacts --release
DESKTOP_RELEASE_WINDOWS_MSI=false bun run release:desktop:windows -- --output-root .desktop-release-artifacts --release
DESKTOP_RELEASE_LINUX_APPIMAGE=false DESKTOP_RELEASE_LINUX_SIGNATURES=false bun run release:desktop:linux-x64 -- --output-root .desktop-release-artifacts --releaseAssemble all platforms:
bun run release:refresh:all-osThis assembles previously built artifacts into packages/desktop/releases/, regenerates checksums, and verifies provenance. The GitHub Actions desktop workflow now gates every native packaging job behind bun ci, bun run lint, bun run typecheck, bun run test, and bun run build, then runs bun run verify:desktop-runtime and bun run verify:desktop-releases -- --release on each native host before artifact upload. MSI, AppImage, and Linux .sig outputs are on by default (unset env uses defaults); set workflow-dispatch inputs, repository variables, or DESKTOP_RELEASE_*=false to turn them off. Extra macOS architectures still use DESKTOP_RELEASE_MACOS_ARCHITECTURES.
--release on macOS always runs xcrun stapler validate on the staged DMG (notarization ticket). For a non-stapled tree (typical local ad-hoc sign), run bun run verify:desktop-releases without --release so payload and checksum checks still run; use --release only when the DMG is stapled like a shipping build.
For the step-by-step proof flow, including production-preview screenshots, PDF first-page rendering with pdftoppm, and Quick Look verification for DOCX exports, follow docs/VERIFICATION_RUNBOOK.md. For the current staged desktop artifact set in this repository, the direct verifier command is:
bun run verify:desktop-releases -- --targets macos,windows,linux-arm64Output locations:
- Raw build output:
packages/desktop/src-tauri/target/release/bundle - Release artifacts:
packages/desktop/releases/{macos,windows,linux-x64,linux-arm64}
The installers above are only downloadable once they are on the public site. docs/index.html
links to releases/<platform>/<file>, releases/sha256.txt and releases/provenance.json,
all of which live in the canonical release tree rather than in docs/ — so the page and the
release tree have to be published together:
git lfs pull # release binaries are LFS objects
bun run docs-site:bundle # stage docs/ + release tree into dist/docs-site
bun run docs-site:deploy # upload over FTPS (credentials from the environment)docs-site:deploy -- --dry-run prints the upload plan without connecting. The bundler fails
closed if any artifact is still an LFS pointer, so a checkout without git lfs pull cannot
publish 133-byte files as installers; pass -- --skip-artifacts for a page-only update.
In CI, .github/workflows/deploy-docs-site.yml publishes automatically after a successful
desktop-release run on main — consuming that run's assembled all-OS artifact set — and on
pushes that touch the site. Credentials come from repository secrets
(BAO_DOCS_FTP_HOST, BAO_DOCS_FTP_USER, BAO_DOCS_FTP_PASSWORD); see
docs/DOCS_SITE_DEPLOY.md.
- Checksums:
packages/desktop/releases/sha256.txt
| Platform | Build target | Notes |
|---|---|---|
macOS (aarch64-apple-darwin) |
release:desktop:macos |
Split flow: bun tauri build --no-bundle then bun tauri bundle --bundles app,dmg; optional x86_64 and universal channels via DESKTOP_RELEASE_MACOS_ARCHITECTURES. |
Windows (x86_64-pc-windows-msvc) |
release:desktop:windows |
NSIS installer + portable zip + MSI by default. Omit MSI with DESKTOP_RELEASE_WINDOWS_MSI=false. |
Linux x64 (x86_64-unknown-linux-gnu) |
release:desktop:linux-x64 |
Deb + RPM + AppImage + detached .sig files by default (when GPG env is set for signing). Omit variants with DESKTOP_RELEASE_LINUX_APPIMAGE=false / DESKTOP_RELEASE_LINUX_SIGNATURES=false. |
Linux ARM64 (aarch64-unknown-linux-gnu) |
release:desktop:linux-arm64 |
Deb + RPM + detached .sig by default. Omit signatures with DESKTOP_RELEASE_LINUX_SIGNATURES=false. Requires ARM64 host or emulated runner. |
For macOS DMG packaging with non-UTF8 locale defaults:
LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 bun run build:desktop| Variable | Default | Purpose |
|---|---|---|
BAO_STACK_BOOTSTRAP_COMMAND |
bun |
Stack command override |
BAO_STACK_HOST |
127.0.0.1 |
Health-check host |
PORT |
3000 |
API port |
CLIENT_PORT |
3001 |
Client readiness check port |
BAO_DISABLE_AUTH |
- | Pass through to stack startup |
BAO_AUTH_SETUP_TOKEN |
- | One-time setup token for first API key bootstrap |
DESKTOP_RELEASE_MACOS_ARCHITECTURES |
aarch64 |
Optional macOS release channels: aarch64, x86_64, universal |
DESKTOP_RELEASE_WINDOWS_MSI |
true |
Include the MSI installable in Windows release builds (false to omit) |
DESKTOP_RELEASE_LINUX_APPIMAGE |
true |
Include the AppImage installable on Linux x64 (false to omit) |
DESKTOP_RELEASE_LINUX_SIGNATURES |
true |
Generate detached GPG signatures for Linux release artifacts (false to omit) |
APPLE_SIGNING_IDENTITY |
- | macOS code-sign identity used only for --release desktop builds |
WINDOWS_CERTIFICATE_THUMBPRINT |
- | Windows certificate thumbprint used only for --release desktop builds |
WINDOWS_DIGEST_ALGORITHM |
SHA256 |
Windows signing digest algorithm for --release desktop builds |
WINDOWS_TIMESTAMP_URL |
- | Windows timestamp server URL for --release desktop builds |
DESKTOP_RELEASE_GPG_KEY_ID |
- | GPG identity for Linux detached signatures |
DESKTOP_RELEASE_GPG_PASSPHRASE |
- | Optional passphrase for Linux detached signatures |
If desktop build fails with failed to run 'cargo metadata':
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh .--------.
/ YOU DIED \ Don't panic. Check the matrix below.
| ________ | Every problem has a save file.
| |CONTINUE| |
| |________| | "Had to be me. Someone else might
\__________/ have gotten it wrong." -- debug carefully.
| Check | Fix |
|---|---|
| Dependencies installed? | bun install |
| Port already in use? | lsof -i :3000 or change PORT in .env |
| DB path writable? | Verify parent directory of DB_PATH exists |
| Need more detail? | Set LOG_LEVEL=debug in .env and restart |
| Check | Fix |
|---|---|
| API base configured? | Verify NUXT_PUBLIC_API_BASE in .env |
| Proxy configured? | Set NUXT_PUBLIC_API_PROXY or ensure 127.0.0.1:${PORT} is reachable |
| CORS issue? | Add client origin to CORS_ORIGINS |
| Server running? | curl http://127.0.0.1:3000/api/health |
| Check | Fix |
|---|---|
| Nuxt bound IPv6-only? | Prefer bun run dev (dev-stack defaults client host to 127.0.0.1). Avoid bare --host localhost if tools dial IPv4. |
| Wrong client base? | Set PAGE_PROOF_CLIENT_BASE=http://127.0.0.1:3001 for proof:browser-smoke / proof:browser-burndown. |
| Chromium missing? | bun run automation:browsers:install (or bunx playwright install chromium). |
| Curl used as UI proof? | Curl proves API only. UI proof requires Playwright screenshots / burndown (see Verification Runbook). |
| Check | Fix |
|---|---|
| WS base correct? | Verify NUXT_PUBLIC_WS_BASE |
| Routes registered? | Server logs should show /api/ws/chat, /api/ws/interview, /api/ws/automation |
| Firewall blocking? | wscat -c ws://localhost:3000/api/ws/chat |
| Check | Fix |
|---|---|
| Playwright browser installed? | bun run automation:browsers:install |
| Chrome available? | which google-chrome or which chromium |
| Script output? | Check server logs for stdout/stderr |
| Run record? | Query /api/automation/runs for the run ID |
| Check | Fix |
|---|---|
| Keys configured? | Verify API keys in .env |
| Local model running? | curl ${LOCAL_MODEL_ENDPOINT}/api/tags |
| Provider logs? | Set LOG_LEVEL=debug and check AI service output |
| Context overflow? | Check conversation length in context-manager.ts |
| Check | Fix |
|---|---|
| Providers registered? | Check server logs for provider registration |
| Network access? | Verify outbound HTTP to Greenhouse, Lever, etc. |
| DB seeded? | Run seed if studios table is empty |
| Dedup too aggressive? | Check thresholds in deduplication.ts |
========================================
| FINAL BOSS: DEPLOYMENT READINESS |
| |
| ,%%%, |
| ,%%%` %==-- HP: [==========] |
| ,%%`( '| |
| ,%%@ /\_/ Clear all checks |
| ,%.-"""--, to defeat this |
| %%/ | boss and go live.|
| %' \ / |
| | / | "Finish Him!" |
| | | | |
========================================
-
bun installcompleted successfully -
bun run automation:browsers:installcompleted successfully -
.envpopulated from.env.examplewith environment-specific values -
bun run typecheckpasses -
bun run validate:no-try-catchpasses -
bun run validate:page-seopasses -
bun run validate:i18n-uipasses -
bun run lintpasses -
bun run testpasses -
bun run db:generate+bun run db:pushcomplete -
bun run devstarts both server and client (client reachable athttp://127.0.0.1:3001) -
/api/healthreturns healthy status -
/api/auth/statusresponds -
/api/jobsreturns job list -
/api/automation/runsreturns run records -
/api/ws/chatWebSocket handshake succeeds -
/api/ws/interviewWebSocket handshake succeeds -
/api/ws/automationWebSocket handshake succeeds - AI provider responds (local or cloud)
-
bun run verify:pagespasses -
PAGE_PROOF_CLIENT_BASE=http://127.0.0.1:3001 bun run proof:browser-smokereports 0 failures -
PAGE_PROOF_CLIENT_BASE=http://127.0.0.1:3001 bun run proof:browser-burndownreports 0 findings -
bun run scripts/validate-ascii-geometry.ts README.mdpasses
+============================================================+
| |
| __ __ ___ ____ ____ ___ ___ _ _ |
| | \/ |_ _/ ___/ ___|_ _/ _ \| \ | | |
| | |\/| || |\___ \___ \| | | | | \| | |
| | | | || | ___) |__) | | |_| | |\ | |
| |_| |_|___|____/____/___\___/|_| \_| |
| |
| ____ ___ __ __ ____ _ _____ _____ _____ |
| / ___/ _ \| \/ | _ \| | | ____|_ _| ____| |
| | | | | | | |\/| | |_) | | | _| | | | _| |
| | |__| |_| | | | | __/| |___| |___ | | | |___ |
| \____\___/|_| |_|_| |_____|_____| |_| |_____| |
| |
| BaoBuildBuddy is ready. |
| |
| "Thank you Mario! |
| But our princess is in |
| another castle." |
| |
| Just kidding. You're done. |
| |
+============================================================+
| Document | Purpose |
|---|---|
| ELI5 System Walkthrough | Plain-English system overview |
| First-Time Setup Guide | Step-by-step first install |
| Local AI Setup Guide | Ollama setup for local AI |
| Automation Guide | RPA contracts and runtime behavior |
| Verification Runbook | Lint/test/build + browser + export + desktop proof |
| Page Verification Guide | Route-by-route screenshot / 5Q checklist |
| Railway Deployment Guide | Deploy to Railway |
| Feature Trace Matrix | Route-to-service-to-UI traceability |
| Stack contract | Canonical SSOT + stack (Drizzle, Nuxt, Eden); wins over .bao-archive playbooks |
| SSOT ledgers | Defect / improvement / baseline / contract-escalation evidence |
| Routing audit | Elysia 2 route registration notes |
| Job Board Service Layer | Job aggregation API reference |
| Server routes | API route modules |
| AGENTS.md | Cursor Cloud / agent operating notes |