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…

这是什么?OpenFrame Console 是 OpenFrame SaaS 平台的 Tauri 2 桌面外壳。它包装 openframe-oss-frontend 的静态网页导出,并添加浏览器标签页做不到的操作系统级功能,例如托盘通知、持久会话和系统浏览器 OAuth 登录。
这是完整应用,还是只是网页前端的包装?这个仓库里没有任何 UI 代码——界面就是前端的静态导出,在构建时放入 www/ 并嵌入二进制文件。这里只有 Rust/Tauri 外壳,围绕嵌入的网页 UI 添加原生操作系统集成。
开发时怎么让它跑起来?运行 `npm run dev`,如果缺少 www/,它会放入一个占位包(generatecontext! 宏在编译时嵌入 www/,没有它就无法构建)。要使用真实的托盘图标、通知和 OAuth 回调处理,你需要运行构建出的 .app/.exe,而不是裸的调试二进制。
构建之前需要装好什么?Rust(stable)及你所在平台的 Tauri 2 系统依赖、Node.js 20+ 和 git(前端导出在构建时克隆)。
它在 Linux 上真的能用吗?它能在 Linux 上构建,但在该平台上既没有系统通知后端,也没有任何 URL scheme 注册,因此通知永远收不到,OAuth 登录回调也无处落地——登录无法完成。
它官方支持哪些平台?macOS 和 Windows 是受支持的目标平台。Linux 能编译,但缺少让登录和通知真正工作所需的通知后端和 URL scheme 处理。
我的登录令牌存在哪里,安全吗?令牌写入配置文件旁边的 tokens.json,仅所有者可读,但以明文存储——README 自己也把钥匙串存储列为已知缺口。
OAuth 登录流程实际上是怎么工作的?它会打开用户的默认浏览器,复用其中已有的 SSO 会话,而不是再次索要凭据。响应通过 openframe-console://auth URL scheme 返回,外壳在原生层完成令牌交换,因为携带令牌的响应头对跨域的 webview fetch 是不可见的。
改名前的 OpenFrame Desktop 安装会怎样?应用标识符从 com.openframe.desktop 改成了 com.openframe.console,所以旧安装会回到未登录状态、丢失登录时启动设置,并重新请求通知权限。外壳会在改名后首次运行时清理过期的登录注册和旧的 openframe-desktop:// URL scheme。
应用会自动更新吗?会——默认情况下它会进行启动时和后台更新检查。可以用 selfupdateenabled: false 禁用,或通过 config.json 里的 updatemanifesturl 重定向到自定义清单。
我登录时它会自动启动吗?会,登录时启动按用户注册(macOS 用 launchd,Windows 用 HKCU Run 注册表项,Linux 用 XDG autostart),默认开启,并以 --autostart 方式直接启动到托盘,而不是打开窗口。
Size
0.9 MB
Stars
156
Age
2mo
Commits · 90 Days