openframe-saas-desktop README

The project's README from github.com/flamingo-stack/openframe-saas-desktop. As one markdown file: README.md.

OpenFrame Console

Native desktop shell for OpenFrame — a Tauri 2 application that embeds the openframe-oss-frontend static export and adds what a browser tab cannot do: OS notifications that keep arriving while the app sits in the tray, a session that survives long idle, and a system-browser OAuth flow.

No UI code lives in this repo. The interface is the frontend's static export, staged into www/ at build time and embedded in the binary. What is here is the Rust shell around it.

Supported: macOS, Windows. Linux builds, but has neither an OS-notification backend nor any URL-scheme registration — so notifications never arrive and the OAuth callback has no way back into the app, leaving sign-in unable to complete.

Requirements

Quick start

npm install
FRONTEND_REF=main npm run build:web   # clone + build openframe-oss-frontend, stage into www/
npm run dev         # tauri dev

npm run dev stages a placeholder bundle when www/ is missing — the generate_context! macro embeds www/ at compile time, so the crate cannot build without one. The placeholder is enough to compile and run the shell, never enough to use the app.

Run the app through npm run dev or a built .app/.exe. The bare target/debug binary is not registered with the OS, so tray icons, notifications and URL-scheme activation behave differently or not at all. Sign-in is one of them: the OAuth callback comes back through the OS on openframe-console://, which macOS routes only to a bundled .app it has seen (launch it once from /Applications) and Windows only to an install that has written its HKCU key.

Building

The Makefile is the entry point; the npm scripts are the primitives it calls. npm run build on its own is a raw tauri build that skips web staging and bakes no host.

make lint     # rustfmt --check + clippy -D warnings
make test     # cargo test
make build OPENFRAME_SHARED_HOST_URL=https://auth.example.com FRONTEND_REF=1.0.100
Variable Purpose
OPENFRAME_SHARED_HOST_URL Required for build. Baked into the binary; see Configuration.
OPENFRAME_VERSION App version, applied via tauri build --config. Defaults to tauri.conf.json.
TARGET Rust target triple for cross-compilation.
BUNDLES Bundle subset, e.g. BUNDLES=app for an unsigned macOS .app.
FRONTEND_DIR Use an existing frontend checkout instead of cloning. No git operations are run against it — this is the local dev loop.
FRONTEND_REF Required unless FRONTEND_DIR is set. Frontend git tag or branch to build; no default, so a build never silently tracks main. A release uses the frontend image tag prod runs (see Releasing).
FRONTEND_REPO Frontend origin, if not the public repo.

Code signing and notarization are deliberately not in the Makefile — they run against the produced artifact, which is why tauri.conf.json sets certificateThumbprint: null.

Releasing

release.yml on workflow_dispatch takes a mandatory frontend_tag: the openframe-oss-frontend image tag prod runs, openframe-saas.frontend.image.tag in manifests/tenant/values-prod.yaml of openframe-saas-tenant. The pipeline resolves the frontend git tag of the same name (the frontend release workflow creates a GitHub release for every released image) and builds the static export from that commit. Pushes to main build the rolling latest prerelease from frontend main, the same source as the dev environment's latest image.

Configuration

The shell knows exactly one configured URL: the shared auth host. There is no tenant host to configure — the frontend's sign-in screen discovers the tenant from the user's email, and the shell is told that tenant's origin once login succeeds.

The shared host is baked in at build time and can be overridden per install by shared_host in config.json, which lives in the OS app-config directory:

OS Path
macOS ~/Library/Application Support/com.openframe.console/config.json
Windows %APPDATA%\com.openframe.console\config.json
Linux ~/.config/com.openframe.console/config.json

Renamed from OpenFrame Desktop (2026-08-26), identifier included: com.openframe.desktop → com.openframe.console. The identifier keys the paths above, the log directory, the macOS notification authorization, the launchd label and the Windows AUMID, so a pre-rename install comes back signed out (its tokens.json stays behind at the old path) and re-prompts for notification permission. That was acceptable because the app had only ever been distributed to test users.

Two leftovers are not inert, and a renamed build deletes both on first run. The login registration is named after the identifier, so an abandoned one keeps launching the old build every login — autostart::remove_legacy_registration clears the macOS/Linux file and the Windows Run value written under OpenFrame Desktop. On Windows the old openframe-desktop:// scheme key still points at the old exe, so a toast left in the Action Center from before the rename would start the build it replaced — remove_legacy_url_scheme clears that and the stale AppUserModelId key. Both can be removed once no machine still has a pre-rename build on it. Nothing else is migrated: the old config directory is left where it is, and installed-agent rows written before the rename stay filed under the old AGENT_TYPE.

Start at Login is reset by the rename, not carried over. autostart_configured lived in the old config directory, so the reconcile sees a renamed install as a first launch and applies the default — which re-enables start-at-login for anyone who had turned it off on a pre-rename build. It cannot be inferred from what the new identity can see: an opted-out pre-rename install and a fresh install both present as "no registration and no record". The old config.json would separate them, but reading it is the migration this rename deliberately does not do. The user's own toggle is authoritative from that point on.

{
  "shared_host": "https://auth.example.com",
  "learned_host": "https://acme.example.com"
}

learned_host is written by the shell after login; it is not meant to be edited. Tokens live next to it in tokens.json (owner-readable only, plaintext — keychain storage is a known gap).

Other keys, all optional:

Key Purpose
self_update_enabled false disables the startup and background update checks.
update_manifest_url Overrides the updater manifest endpoint.
autostart_enforced Pins Start at Login on (true) or off (false) for a managed install, and takes the toggle away from the user. Absent leaves the choice to them.
autostart_configured Shell-written. Records that the start-at-login default has been applied, so it is applied once rather than re-asserted over a user who turned it off.
autostart_show_window_next_start Shell-written, cleared on the next launch. Set by an update that restarts while a window is open, so the relaunch shows one even in a login session.

Gateway prerequisite: CORS must allow the shell's origins — tauri://localhost (macOS/Linux) and http://tauri.localhost (Windows) — including exposing the Access-Token and Refresh-Token response headers. Without it the UI renders and every data call fails.

What the shell owns

See docs/auth-and-notifications.md for how those work, and docs/architecture.md for the shell itself — windows, the bundle pipeline, and the security model.

Layout

Makefile              build entry point
scripts/
  build-web.sh        clone/build the frontend export → www/
  make-placeholder-web.mjs
src-tauri/
  src/lib.rs          windows, tray, config, NativeAuth commands
  src/tokens.rs       token custody, refresh, rotation events
  src/nats.rs         NATS-over-WebSocket connection lifecycle
  src/notifications.rs  subscription, dispatch, click delivery
  src/notification_actions.rs  what a notification button does, and its session gate
  src/chat_api.rs     the REST calls a button completes without a window
  src/macos_un.rs     UNUserNotificationCenter backend
  src/windows_toast.rs  toast XML + the button-argument codec
  src/windows_activator.rs  COM activator the toast buttons activate
  src/autostart.rs    start-at-login registration and the headless start
  src/updater.rs      silent startup update, background poll, update commands
www/                  staged frontend bundle (generated, git-ignored)

openframe-saas-desktop (flamingo-stack/openframe-saas-desktop)

Searching Database…

What is this?OpenFrame Console is a Tauri 2 desktop shell for the OpenFrame SaaS platform. It wraps the openframe-oss-frontend static web export and adds OS-level features a browser tab can't do, like tray notifications, persistent sessions, and system-browser OAuth sign-in.
Is this a full app or just a wrapper around a web frontend?No UI code lives in this repo — the interface is the frontend's static export, staged into www/ at build time and embedded in the binary. What's here is only the Rust/Tauri shell that adds native OS integration around that embedded web UI.
How do I get this running for development?Run `npm run dev`, which stages a placeholder bundle if www/ is missing (the generatecontext! macro embeds www/ at compile time, so it can't build without one). For real tray icons, notifications, and OAuth callback handling you need to run a built .app/.exe, not the bare debug binary.
What do I need installed before building it?Rust (stable) with the Tauri 2 system prerequisites for your platform, Node.js 20+, and git (the frontend export is cloned at build time).
Does it actually work on Linux?It builds on Linux, but has neither an OS-notification backend nor any URL-scheme registration there, so notifications never arrive and the OAuth sign-in callback has nowhere to land — login can't complete.
Which platforms does it officially support?macOS and Windows are the supported targets. Linux compiles but is missing the notification backend and URL-scheme handling needed for login and notifications to actually work.
Where are my login tokens stored, and are they safe?Tokens are written to tokens.json next to the config file, owner-readable only but stored in plaintext — the README itself calls keychain storage a known gap.
How does the OAuth sign-in flow actually work?It opens the user's default browser so an existing SSO session there is reused instead of asking for credentials again. The response comes back through the openframe-console://auth URL scheme, and the shell exchanges tokens natively, since the response headers carrying them are invisible to a cross-origin webview fetch.
What happened to pre-rename 'OpenFrame Desktop' installs?The app identifier changed from com.openframe.desktop to com.openframe.console, so old installs come back signed out, lose their Start-at-Login state, and re-prompt for notification permission. The shell cleans up the stale login registration and the old openframe-desktop:// URL scheme on first run after the rename.
Does the app update itself automatically?Yes — it runs startup and background update checks by default. This can be disabled with selfupdateenabled: false or redirected to a custom manifest via updatemanifesturl in config.json.
Does it start automatically when I log in?Yes, Start at Login is registered per-user (launchd on macOS, an HKCU Run entry on Windows, XDG autostart on Linux) and is on by default, launching straight into the tray with --autostart rather than opening a window.
Size
0.9 MB
Stars
156
Age
2mo
Commits · 90 Days