A local home for every app.
Latte runs PostgreSQL, DNS, and HTTPS on your Mac, and Frappé builds, serves, and checks each application at its own named origin.
What Latte runs
Latte is a per-user service supervisor for macOS. It runs one shared PostgreSQL 18 cluster on a private Unix socket, CoreDNS, and a Caddy HTTPS proxy, and keeps a registry of your sites. Applications run under frappe dev in your terminal, not under Latte.
When a Frappé command needs the services and Latte is not running, Frappé runs latte daemon --detach. The daemon outlives the terminal. Stopping it leaves the services running, and the next daemon adopts them.
frappe services # postgres, dns and proxy with their states
frappe services start # starts Latte when needed, then its services
frappe services stop # stops the services; Latte keeps running
latte stop # stops Latte; the services keep running
latte service install # start Latte at every login
latte service uninstall
latte versionThe login item is opt-in. latte service install writes ~/Library/LaunchAgents/dev.caramel.latte.plist, takes over from a running daemon, and starts Latte now. latte daemon without --detach runs Latte in the current terminal.
Latte’s logs live in ~/Library/Application Support/Caramel/logs/: latte.log for the daemon, and postgres.log, dns.log, and proxy.log for the services. Past one MiB, a log’s last MiB moves to a .previous file.
The menu bar app shows the services and sites, opens a site, its folder, or its logs, and starts or stops the services. It does not start Latte. Build it from a Caramel checkout with scripts/build-latte-menu, which writes bin/Latte.app.
Set up a Mac once
Caramel 0.7.1 runs on Apple Silicon with Apple’s Command Line Tools, and installs from source. From a checkout of the release, these steps install the pinned toolchain, put frappe and latte in ~/.local/bin, and connect .caramel names to Latte.
scripts/install-toolchain
scripts/build-release
bin/frappe installations register
frappe services start
scripts/install-local-integration prepare /private/tmp/caramel-integration-bundle
sudo scripts/install-local-integration apply /private/tmp/caramel-integration-bundle
latte trust installprepare builds the integration bundle as you, and apply installs it as an administrator. It adds the resolver /etc/resolver/caramel, which sends .caramel lookups to Latte’s DNS on port 15353, and a launchd relay from ports 80 and 443 to Caddy on 18080 and 18443. latte trust install adds Latte’s certificate authority to your default keychain. sudo scripts/install-local-integration uninstall and latte trust remove undo these steps.
Sites and origins
frappe new NAME registers the application with Latte as a site, writes its private .env, and applies its migrations. Its origin is https://NAME.caramel. In a cloned project, frappe setup installs the locked dependencies and takes the same steps, keeping existing files.
frappe sites
frappe sites remove demo
frappe openfrappe sites prints each site’s name, state, origin, and folder; (terminal) marks a site that a frappe dev session serves. frappe sites remove NAME unregisters a site but keeps its folder, databases, credentials, logs, and backups; frappe setup registers it again. frappe open opens the origin in your browser.
The suffix is domain_suffix in config/environment.yml: caramel, test, or localhost. macOS and browsers resolve .localhost names to loopback without a resolver entry. To switch, change the suffix, delete .env, and run frappe setup, which registers a new site with its own databases.
That removes only the resolver step. frappe dev, frappe open, and frappe doctor require the origin to reach loopback with trusted HTTPS on the standard port, so a .localhost site still needs the port relay and latte trust install.
The development loop
frappe dev builds the application, serves it at its origin, and opens the browser when the first build is ready. It watches src, config, db, app, public, shard.yml, shard.lock, and .env with kqueue. Ctrl-C stops the project. One development session runs per project at a time.
frappe dev
frappe dev --no-open
frappe dev --branch experimentA source change waits 50 ms for related saves, then runs one compiler build with -D caramel_development. The terminal prints Type check passed or Type check failed as soon as the compiler finishes its semantic stages, and a failure skips code generation. A newer change stops an obsolete build. Asset changes alone refresh the page without a rebuild.
While the application builds or fails, its origin answers 503 with a diagnostics page showing the compiler’s output, with secrets and database URLs redacted. The page reloads when the next build is ready. With pending migrations, frappe dev says so once and serves as soon as frappe migrate applies them. A changed .env needs a restart. --branch NAME runs against a database branch that frappe db branch create NAME made.
frappe logs
frappe logs compiler --follow
frappe doctorfrappe logs prints the last 200 lines of the application or compiler log, and --follow keeps printing. These logs live in ~/Library/Application Support/Caramel/logs/sites/. frappe doctor checks the toolchain, this installation, dependencies, .env, Latte’s services, trusted HTTPS, and the port relay. It prints OK or CHECK for each, and exits 1 when any check fails.
Databases and environment files
Latte gives each site a development database and a separate spec database, each with a runtime URL and a migration URL. Setup writes them to .env with APP_ORIGIN and a fresh APP_SECRET. .env is private (mode 0600) and ignored by Git; commands refuse to run when its database URLs differ from Latte’s.
.envPrivate: the origin, the secret, and the development and spec database URLs.env.testCommitted test-only settings for every spec workerconfig/environment.ymlSite name, PostgreSQL major, extensions, and domain suffixdb/seeds.crSeed data for frappe seedSpecs never touch development data. frappe corretto migrates the spec database, has Latte clone one for each worker, and gives each worker the values in .env.test. That file cannot set Caramel’s own keys, such as APP_SECRET, DATABASE_URL, or any SPEC_ key.
frappe seed
frappe db dump
frappe db restore backup.dump
frappe db branch create experimentfrappe seed runs App.seed from db/seeds.cr against the development database, and never resets it. frappe db dump saves a backup in ~/Library/Application Support/Caramel/backups/, in a folder for the site. frappe db restore FILE first saves the current database as a pre-restore backup, then restores FILE. It refuses while a development session runs. A branch is a copy-on-write clone of the development database.
Installations and pinned releases
An application’s shard.lock pins its Caramel release, and Frappé hands each project command to that release’s Frappé. A missing release is offered for install on a terminal; agents and pipes get the install command. Commands that need no project, such as frappe sites, run in the Frappé you called.
frappe installations
frappe installations install 0.7.1
frappe installations register
frappe installations remove 0.7.1install clones the release’s tag into ~/Library/Application Support/Caramel/releases/, reuses your toolchain when the release pins the same one, builds it, and registers it. On an installed release, it builds what is missing, such as its linter. register records the current checkout, which needs a built bin/frappe and bin/latte. remove forgets a release.
The launchers in ~/.local/bin, on-demand Latte, and the login item run the newest installed release. The resolver and port relay must match it too; frappe doctor prints the commands that replace them when they differ.
Editor tools
Each installation pins two language servers that use its private toolchain: crystalline 0.20.0 for definitions, hover, diagnostics, and formatting, and ameba-ls 0.2.0 for lint diagnostics.
frappe lsp installThe first install needs network access and git, and builds crystalline from source in up to about 20 minutes. frappe lsp crystalline and frappe lsp ameba-ls run a server on stdio for the current project, after checking its pinned release. Any editor can run them from the project folder.
.zed/settings.jsonGenerated: runs both servers through frappe in Zed, which must find frappe on its login-shell PATH