A NestJS backend, an Angular frontend, and a Tauri desktop shell in one npm workspace — all three start with a single command.
New here? CONTRIBUTING.md is the short path from a
clone to a merged pull request, and there is
a backlog of self-contained issues
waiting for someone to pick them up.
npm install
npm run devnpm run dev boots the API, boots the Angular dev server, waits until both
answer, then opens the Tauri window. Closing the window shuts the other two
down.
glassbeetle/
├── apps/
│ ├── api/ NestJS 12 backend → http://localhost:3000/api/v1
│ ├── web/ Angular 21 frontend → http://localhost:4200
│ └── desktop/src-tauri/ Tauri 2 shell (Rust)
├── data/ The SQL schema, one file per table
├── docs/ Conventions and design records
├── scripts/ Repository tooling (npm run stop)
└── package.json npm workspaces + orchestration scripts
The API is the only process that opens the SQLite database — not the frontend,
not the Rust shell. data/ holds the schema as executable SQL, applied in the
order declared in
apps/api/src/database/schema-migrations.ts.
The documentation index points to the project's conventions,
design records, and proposals.
The three tiers talk to each other like this:
- The Tauri window loads
http://localhost:4200in development, and the prebuilt Angular bundle (apps/web/dist/web/browser) in a packaged build. - Angular calls the API at
http://localhost:3000/api/v1, configured inapps/web/src/environments/. The URL is absolute on purpose: a packaged Tauri app is served fromtauri://localhost, so a relative/apiwould resolve against the custom protocol instead of the NestJS server. - NestJS allows exactly three CORS origins — the dev server, the macOS/Linux
webview (
tauri://localhost) and the Windows webview (http://tauri.localhost). The API binds to127.0.0.1and has no authentication, so the allowlist is what stops any website the user visits from reading their data overlocalhost. Override it withGLASSBEETLE_CORS_ORIGINSif you run the frontend on a different port. - Rust and Angular talk through events rather than through a command surface.
The native menu emits
glassbeetle://menuwith an action id, which the Angular shell carries out; the shell emitsglassbeetle://readyonce it has painted, which is what makes the window appear. Seeapps/desktop/src-tauri/src/menu.rs.
| Command | What it does |
|---|---|
npm run dev |
API + Angular + Tauri window (the one command) |
npm start |
Alias for npm run dev |
npm run dev:api |
Just the NestJS API, in watch mode |
npm run dev:web |
Just the Angular dev server |
npm run stop |
Stop the API, the dev server and the Tauri window |
npm run build |
Compile the API and build the Angular bundle |
npm run build:desktop |
Build the API, then bundle the installable desktop app |
npm test |
Run the API and frontend unit tests |
npm run test:e2e -w @glassbeetle/api |
Run the API end-to-end tests |
npm run openapi:export -w @glassbeetle/api |
Write the OpenAPI spec to a file |
npm run clean |
Remove dist/ output and the Rust target/ directory |
npm run dev shuts all three tiers down when you close the Tauri window, but a
crashed terminal or a detached run can leave the API or the dev server holding
their port — and the next start then fails with EADDRINUSE. npm run stop
clears that: it finds whatever is listening on 3000 and 4200, stops the
watchers as well as the servers so nothing restarts itself, and leaves other
projects alone. It is safe to run when nothing is up.
npm run stop -- --dry-run # show what would be stopped, kill nothing
API_PORT=3001 npm run stop # non-default portsThe UI is an agent workspace built on Angular and Angular Material: a rail carrying the live agent roster beside every screen, a ⌘K command palette over agents, memory and prompts, and a workspace per agent showing the context it is configured to draw on.
It covers the endpoints the API implements — the user's own profile, the spending budget, agents (profile picture and private memories included), teams and their rosters, chats, shared memory, system prompts and service health. The modules that are still empty controllers have no screens.
Where the schema promises more than the API serves, the UI says so rather than
mocking it: agents show configuration readiness rather than an invented run
state, the feed is labelled as configuration history, the spending screen
states that nothing writes usage_events yet rather than presenting an
untouched budget as a measurement, the chat surface manages conversations and
says where the transcript will go rather than drawing a composer that has no
endpoint to send to, and model assignment is disabled with its reason stated
because agents.model_id references a models table that has no endpoint to
populate it.
Structure, conventions and the capability probe that drives that last point are
documented in apps/web/README.md. Read it before adding
a screen.
The API is served under /api/v1 and is documented with OpenAPI:
- interactive docs — http://localhost:3000/api/docs
- raw document — http://localhost:3000/api/docs-json, or
npm run openapi:export -w @glassbeetle/api
Conventions every endpoint follows — routing, resource representation,
validation, the error envelope, configuration and security boundaries — are
documented in docs/api-conventions.md. Read it
before adding an endpoint.
Every setting is optional; the API starts with per-user defaults and does not
require a .env file. See apps/api/.env.example for
the full list.
Application data (the SQLite database, uploaded files, artifacts and backups) lives outside the repository, in a per-user directory:
| Platform | Default data directory |
|---|---|
| macOS | ~/Library/Application Support/Glassbeetle |
| Windows | %APPDATA%\Glassbeetle |
| Linux | $XDG_DATA_HOME/glassbeetle, else ~/.local/share/glassbeetle |
Override it with GLASSBEETLE_DATA_DIR (absolute paths only).
- Node.js ≥ 20.19 (see the note below about newer Angular)
- Rust toolchain (
rustup), plus the platform prerequisites for Tauri 2 — Xcode Command Line Tools on macOS
npm run build:desktop produces an installable app, but the bundle contains
only the frontend and the Rust shell — it does not embed the NestJS server. A
packaged build expects the API to already be running on port 3000. To ship the
backend inside the installer, compile it to a binary and register it as a
Tauri sidecar.
Glassbeetle is meant to be built by more than one person, and the backlog is deliberately sliced so that you can finish a piece of it without reading the whole codebase. Most open issues name a single endpoint or a single screen and come with acceptance criteria already written.
- Contributing guide — setup, the fast dev loop without Rust or Tauri, the conventions, and what makes a pull request easy to merge
- Good first issues — scoped for a first contribution
- Contributors — everyone who has helped build this
- Code of Conduct · Security policy
Questions are welcome as issues, and an unfinished change is welcome as a draft pull request. You do not need permission to start.
Conventions live next to the code they govern:
docs/api-conventions.md before adding an endpoint,
apps/web/README.md before adding a screen,
docs/testing.md before writing a test.
Two things on this machine are pinned by the installed Node version (24.2.0):
- Angular is on 21, not 22. Angular 22 requires Node
^22.22.3 || ^24.15.0. Upgrading Node to a current release lets young updateto 22. package-lock.jsonwas generated with a newer npm. npm 11.3.0 (the version bundled with Node 24.2.0) crashes withCannot read properties of null (reading 'edgesOut')while resolving Vitest's peer graph from scratch. Installing from the committed lockfile works fine. If you ever need to regenerate it before upgrading Node, usenpx npm@latest install.
Upgrading Node to a current LTS resolves both.
MIT. Contributions are accepted under the same terms.