Skip to content

add a desktop app around the hosted web app - #173

Merged
arnestrickmann merged 8 commits into
mainfrom
hkonsti/pap-electron-shell
Nov 3, 2026
Merged

arnestrickmann merged 8 commits into
mainfrom
hkonsti/pap-electron-shell

Conversation

@hkonsti

@hkonsti hkonsti commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Adds packages/desktop, an Electron window around hosted Personal Agent Proxy with nothing of the web app or the api bundled, in five commits: signing the app in through the browser (a PKCE one-time code handed back through personalagentproxy://), the app itself (origin allowlist, its own sign-in and offline pages, deep links, window state), connecting services through the browser (a one-time handoff signs the browser in as the same person, the sign-in runs there marked ?desktop=1, and /desktop-return hands back to the app), packaging and a desktop-v* release workflow for macOS arm64 and x64 with electron-updater on the repository's latest release, and Plaid Link's OAuth popup as a window of the app's.

Service sign-ins have to leave the window because Google turns embedded browsers away and the handshake lives in a cookie, so the whole of a sign-in runs in one browser. Two migrations add the code tables (v039, v040); codes are hashed, short-lived and single-use. Builds are unsigned until there is an Apple Developer account, which desktop.yml picks up from secrets; bun run desktop runs the app on a local or bound dev stack.

Tested on Linux under Xvfb end to end (sign-in loop, hand-over to a real Google sign-in page and back, the custom MCP form moving to the browser, the popup mechanics with a cdn.plaid.com frame) and with a mac zip packed by electron-builder; not yet run on macOS, and the Plaid round trip needs sandbox keys.


Open workspace in Conductor

The desktop app (next commit) can't sign in itself: Google turns embedded
browsers away, and a magic link opens in the browser anyway. So it keeps a
PKCE verifier and opens the browser at /desktop-auth?challenge=, where the
person signs in as usual. The page mints a one-time code bound to the
challenge (POST /auth/desktop/code) and hands it over through the app's
personalagentproxy:// link; the app trades code and verifier for a session
cookie of its own (POST /auth/desktop/session). Codes last five minutes,
are kept only as their hash, and are taken only once the verifier checks
out, so a wrong one leaves the code to the app that holds the right one.
packages/desktop: Electron, with nothing of the web app or the api in it.
The main process keeps the window to the app's own origin (links out of
it open in the browser), shows a page of its own where the app would show
its login page, with a button that signs in through the browser (previous
commit), and takes the personalagentproxy:// link the browser comes back
with. A page for no connection, and the window's last size and place.

`bun run desktop` runs it on this checkout's web app, using a `bun run
dev` already running or starting the stack itself. CI skips the Electron
download its install would make.
A service's sign-in page turns the app's window away, as Google's does,
and the sign-in's handshake lives in a cookie, so the whole of it has to
happen in one browser. From the desktop app it now runs in the person's
browser: the web app, which knows the window by its user agent, asks the
api for a handoff (POST /auth/desktop/handoff), an address with a
one-time code that signs the browser in as the same person, unless it is
already, and goes on to the service's sign-in, marked ?desktop=1. That
sign-in ends on /desktop-return, which hands back through
personalagentproxy://open?path=, and the window opens the page the
sign-in would have ended on, with its error where it did not finish.

A custom MCP server that asks for a sign-in moves its form to the
browser, filled in as it was, to be sent again there.
electron-builder.yml builds the macOS app for Apple silicon and Intel, a
disk image and a zip each, with latest-mac.yml, the feed electron-updater
checks the repository's latest GitHub release for (src/updates.ts). The
desktop workflow builds on a desktop-v<version> tag, checks it names the
version in package.json, and publishes the files as the release; a pull
request touching the app only builds it. Signing and notarization start
once Apple's certificate and credentials are secrets; until then the
build is unsigned, which macOS warns about and the updater can't update.
A first icon from the brand mark.
Plaid Link runs in its frame over the page, and signs in to an OAuth
bank in a popup that reports back to it and closes, which only a window
of the app's can do; the desktop app sent every popup to the browser,
where Link never heard from it. With Link's frame on the page, the page
under it inert, a popup is Link's and becomes a window of the app's,
whose own links still go to the browser.
@hkonsti
hkonsti force-pushed the hkonsti/pap-electron-shell branch from f542878 to 7227116 Compare November 3, 2026 22:35
hkonsti and others added 3 commits November 3, 2026 23:06
…n-in code

The handoff let the app mint a link that signed whichever browser opened it in
as the app's person, which any signed-in user could send to anyone else. The
browser now goes through /login?callbackUrl=, which sends a signed-in browser
straight on and lets any other sign in as itself first.
Every "Sign in again" link hands off to the browser, and the window turns any
redirect out of the app's origin into the browser running that page. A custom
MCP server's sign-in begun from the app ends back in it, and its form no longer
waits on the browser. A second press of the sign-in button shares the first's
verifier. Startup takes an early return instead of an else block, and
AGENTS.md lists bun run desktop.
@arnestrickmann
arnestrickmann merged commit f1df06b into main Nov 3, 2026
4 checks passed
@hkonsti
hkonsti deleted the hkonsti/pap-electron-shell branch November 4, 2026 01:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants