- TypeScript 96.9%
- Nix 2.1%
- CSS 0.8%
- HTML 0.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| .forgejo/workflows | ||
| apps | ||
| nix | ||
| scripts | ||
| .editorconfig | ||
| .gitattributes | ||
| .gitignore | ||
| .gitmodules | ||
| deno.json | ||
| deno.lock | ||
| devenv.lock | ||
| devenv.nix | ||
| devenv.yaml | ||
| flake.lock | ||
| flake.nix | ||
| LICENSE-APACHE-2.0 | ||
| LICENSE-MIT | ||
| playwright-test.ts | ||
| playwright.config.ts | ||
| README.md | ||
| secretspec.toml | ||
| tsconfig.json | ||
Bark Surface (cmugpt-surface)
Bark Surface is the user-facing web app and BFF (backend-for-frontend) API for
Bark, ScottyLabs' CMU-focused AI chat assistant. It handles authentication,
chat history, memory management, and streams chat responses from the
cmugpt-agent service, which does the actual agent/LLM
work.
This repo is built on ScottyStack, ScottyLabs' full-stack typesafe template, so the general ScottyStack docs also apply here. This README covers what's specific to this project.
Repo structure
This is a Deno workspace with two apps and no separate agent
code - the actual chat/LLM logic lives in the sibling cmugpt-agent repo.
cmugpt-surface/
|-- apps/
| |-- server/ # Express API (BFF) - auth, chat, memory
| | |-- src/
| | | |-- server.ts # entrypoint: migrations, middleware, routes
| | | |-- env.ts # zod-validated environment schema
| | | |-- controllers/ # tsoa controllers (source of truth for the API)
| | | |-- routes/ # hand-written routes (SSE streaming, auth)
| | | |-- services/ # business logic (chat, memory, preferences)
| | | |-- middlewares/ # error handling, 404s
| | | |-- lib/ # OIDC client, agent HTTP client, auth helpers
| | | `-- db/ # drizzle schema + client
| | |-- drizzle/ # SQL migrations (drizzle-kit)
| | `-- build/ # generated: OpenAPI spec, routes, types
| `-- web/ # React SPA
| |-- src/
| | |-- routes/ # TanStack Router file-based routes
| | |-- components/ # chat UI, memory manager, icons
| | |-- integrations/ # auth + TanStack Query wiring
| | `-- lib/api/ # typed API client (generated from server OpenAPI)
| `-- e2e/ # Playwright end-to-end tests
|-- nix/, devenv.nix, flake.nix # Nix/devenv dev environment + build packages
|-- scripts/secrets/ # git submodule: secrets-sync-scripts
`-- secretspec.toml # declares required env vars / secrets
Key thing to know: apps/server is the source of truth for the API shape.
tsoa generates an OpenAPI spec + Express routes from the controllers in
apps/server/src/controllers, and apps/web imports the generated
openapi.d.ts types directly (see the generate task below) to get a fully
typed API client with no manual API type definitions on the frontend.
Tech stack
| Layer | Tech |
|---|---|
| Runtime / package manager | Deno (workspace of two deno.json projects, npm deps via npm: specifiers) |
| API server | Express 5, tsoa (decorators to OpenAPI + routes), Zod |
| Database | PostgreSQL + pgvector (semantic memory search), Drizzle ORM + drizzle-kit |
| Auth | OIDC (Andrew ID SSO) via openid-client/jwks-rsa, plus lightweight server-side sessions (better-auth-shaped tables) |
| Frontend | React 19, Vite, TanStack Router + TanStack Query, Tailwind CSS v4 |
| API contract | Auto-generated OpenAPI spec, consumed via openapi-fetch + openapi-react-query |
| Testing | Vitest (unit, web), Playwright (e2e, apps/web/e2e) |
| Dev environment | devenv + Nix, via ScottyLabs' internal kennel devenv modules |
| Secrets | secretspec - local .env or ScottyLabs' OpenBao vault |
| CI/CD | Forgejo Actions, reusing ScottyLabs/kennel's shared CI workflow |
Before you start
Complete the ScottyLabs setup on docs.scottylabs.org first:
- A git.cmu.dev account with SSH set up. See Forgejo Setup.
- Membership in the
slaiteam. Contributing explains how to join a team through governance: add your git.cmu.dev username tomembersindata/teams/slai.tomland open a PR. This membership is what grants read access to the project's dev secrets (OIDC client, agent secret, and so on) in OpenBao. Without it you can log in, but secrets fail to load. Credentials and Kennel's Secrets guide describe how these secrets are stored and loaded. - Nix and devenv (below). The shell provides Deno, Postgres with pgvector,
the local login relay,
bao, andsecretspec, so you don't need to install those yourself. You only need Git.
Installing Nix and devenv
-
Nix, with the Determinate Systems installer on macOS, Linux, or WSL:
curl -fsSL https://install.determinate.systems/nix | sh -s -- install -
devenv, from a new terminal:
nix profile install nixpkgs#devenv -
Optionally, direnv with its shell hook, so the environment loads when you
cdinto the repo.
Logging in to OpenBao (once per machine)
The devenv shell loads secrets from ScottyLabs' OpenBao as it starts, so it
won't build until you're logged in. Log in once with the standalone
command (it runs before you have bao installed):
nix run git+https://git.cmu.dev/ScottyLabs/kennel#login
A browser window opens for ScottyLabs SSO. The token renews each time you enter the shell, so you only need to log in again on a new machine or after 90 days without using it.
Running it
With devenv (recommended)
git clone ssh://forgejo@git.cmu.dev/ScottyLabs/cmugpt-surface.git
cd cmugpt-surface
devenv shell # or `direnv allow` if you use direnv
devenv up
Don't clone with --recurse-submodules. scripts/secrets is a legacy
submodule from the old Vault setup that points at a GitHub SSH URL, and you
don't need it.
devenv shell only builds the environment and loads secrets. devenv up
starts every process defined in devenv.nix and the shared
ScottyLabs module:
postgres: Postgres with pgvector (devenv exportsDATABASE_URL)ricochet: the local OAuth relay on127.0.0.1:8090. Andrew ID login won't work locally without it.api: the server on:3001web: Vite on http://localhost:4173
To run the apps yourself (for example, to see their output in your own terminal), start only the supporting services and run Deno in another devenv shell:
devenv up postgres ricochet # terminal 1
deno install && PORT=3001 deno task dev # terminal 2
PORT=3001 matters: devenv only sets it for its own api process, and without
it the server falls back to port 80, which the Vite dev proxy doesn't target.
By default the server talks to the deployed agent at
https://api.cmugpt-agent.scottylabs.org. To use a local agent, run
cmugpt-agent with devenv up
and set AGENT_API_URL=http://localhost:5055 before starting the server. The
agent's README covers its own setup.
Troubleshooting
- Shell fails with an OpenBao, secretspec, or permission error. Your
OpenBao token is missing or expired: re-run the
nix run ...#logincommand above. If login works but secrets still fail, you're probably not in theslaiteam yet (see Before you start). - Check which secrets resolve (reports whether each is present, never the
values):
secretspec check -P dev - Server crashes on startup with a zod error. A required variable is
missing. Usually
DATABASE_URLis unset because Postgres wasn't started throughdevenv up. - See full startup logs:
devenv --no-tui shell
Without devenv (not recommended)
Nix is the supported path. Without it, Andrew ID login won't work locally,
because the ricochet relay it depends on is provided by Nix. You can still
work on the parts that don't need login.
-
Install:
- Git
- Deno
- PostgreSQL and the
pgvector extension
(
brew install postgresql@17 pgvectoron macOS) - OpenBao (
brew install openbaoon macOS) - secretspec
(
curl -sSL https://install.secretspec.dev | sh)
-
Create a database with pgvector:
createdb cmugpt_surface psql -d cmugpt_surface -c 'CREATE EXTENSION IF NOT EXISTS vector;' export DATABASE_URL='postgresql:///cmugpt_surface?host=/tmp' export PORT=3001 -
Log in to OpenBao (needs
slaiteam membership) and run with the dev secrets injected:BAO_ADDR=https://secrets.scottylabs.org bao login -method=oidc deno install secretspec run -P dev -- deno task dev
Migrations run automatically on server startup (apps/server/src/server.ts
calls drizzle-orm's migrate() against apps/server/drizzle before the
Express app starts listening).
Environment variables
Declared in secretspec.toml and validated at runtime by
apps/server/src/env.ts:
| Variable | Required | Description |
|---|---|---|
DATABASE_URL |
yes | Postgres connection string |
OIDC_ISSUER_URL |
yes | OIDC issuer for Andrew ID SSO |
OIDC_CLIENT_ID |
yes (default sl-ai-local) |
OIDC client ID |
OIDC_CLIENT_SECRET |
yes | OIDC client secret |
ALLOWED_ORIGINS_REGEX |
yes | Regex of allowed CORS origins |
AGENT_API_URL |
yes (default https://api.cmugpt-agent.scottylabs.org) |
Base URL of the cmugpt-agent service |
AGENT_SHARED_SECRET |
prod only, but strongly recommended locally | App-to-app bearer secret shared with cmugpt-agent; must not use a VITE_-prefixed name or otherwise reach the browser. Generate with openssl rand -hex 32. Required to be >=32 chars with no leading/trailing whitespace in production. |
OAUTH_RELAY_URL |
no | Shared "ricochet" OAuth relay callback, used so preview deployments can authenticate without registering their own redirect URI |
PORT / SERVER_PORT |
no (default 80; devenv sets PORT=3001) |
API server port. PORT takes precedence. |
APP_URL |
no (default http://localhost:4173) |
Public URL of the web app, used for OIDC callback construction |
ADMIN_GROUP |
no | Admin group name (stopgap until real governance wiring exists) |
See apps/server/README.md for more on the agent-connection secret.
Common tasks
Run from the repo root (these fan out to the two workspace apps):
| Command | Description |
|---|---|
deno task dev |
Run server + web dev servers concurrently (outside devenv up, prefix with PORT=3001) |
deno task generate |
Regenerate the OpenAPI spec/routes from server controllers (also runs automatically before build/dev) |
deno task build |
Build both apps for production |
deno task check |
Type-check both apps (deno check) |
deno task test:e2e |
Run Playwright e2e tests against the web app |
deno task db:generate |
Generate a new Drizzle migration from schema changes |
deno task db:migrate |
Apply pending migrations |
Per-app tasks (run with deno task --cwd apps/<app> <task>, or cd into the
app first):
apps/server:server(no regen),tsoa:watch,openapi:watch,start,checkapps/web:dev,build,preview,test(Vitest unit tests),check
Testing
- Unit tests (
apps/web):deno task --cwd apps/web test(Vitest + Testing Library) - E2E tests:
deno task test:e2efrom the repo root, powered by playwright.config.ts. It boots the web dev server automatically and runs specs inapps/web/e2e/. - Type checking:
deno task check
Deployment
Deployed via ScottyLabs' internal kennel platform (see the kennel block
in devenv.nix):
- Web (SPA) ->
bark.scottylabs.org - API service ->
api.cmugpt.com
flake.nix defines Nix packages (web, api) used to build
production artifacts - the API compiles to a standalone Deno binary via
deno compile.
Related repos
cmugpt-agent- the actual chat agent/LLM service this server proxies to (OpenRouter for chat, OpenAI embeddings for memory search, pgvector-backed long-term memory).
License
Dual-licensed under Apache-2.0 or MIT, at your option.