Skip to main content

Desktop and cloud

Genie has one frontend and three ways to host it: the Electron desktop app with an embedded backend, the local web app (see Getting started), and a cloud web build on AWS. This page covers the desktop app, the cloud deployment and the local cloud simulator.

note

Team policy: test cloud changes against the local cloud simulator (genie-local-cloud) and update the CDK code without deploying it. Do not change the live AWS environment without explicit permission from the project owner. When new AWS resources are needed, write the AWS CLI or CDK commands into a file for review instead of running them.

Desktop app (Electron)​

PropertyValue
Packageapps/desktop (genie-desktop)
Electron^35.0.0
App idcom.geniegroup.genie.desktop
Product nameGenie
Entrysrc/bootstrap.cjs, which imports src/main.js
PlatformmacOS first (dmg and zip targets)

Startup​

  1. bootstrap.cjs starts the startup log, registers handlers for uncaught exceptions and unhandled rejections, and imports main.js.
  2. main.js looks for the Codex CLI (see below). If it is missing, the app logs that assistant features will be disabled and continues.
  3. It starts the backend in the same process with startLocalBackend from genie-backend:
    • host 127.0.0.1, port 0 (a random free port)
    • session data in session-data/ under Electron's userData folder
    • the renderer build as the static root, so the backend serves the frontend
    • GENIE_CLOUD_API_URL passed through if set
  4. It opens the main window and loads http://127.0.0.1:<port>. Because the page is served by the backend and runs inside Electron, the frontend uses same-origin /ws for chat and the same backend for project data.

If the backend fails to start, the window shows a "Genie Desktop could not start" page with the error and a hint about GENIE_CODEX_COMMAND.

When you quit, the app closes the backend first, which ends every Codex app-server process. On macOS, closing the last window does not quit the app; activating it again reopens the window.

Window​

SettingValue
Size1480 × 980, minimum 1100 × 760
contextIsolationtrue
sandboxtrue
nodeIntegrationfalse
Preloadsrc/preload.cjs

Preload API​

The preload script exposes window.genieDesktop:

MemberReturnsPurpose
target"electron"Lets the frontend detect the desktop app
pickDirectories()Promise<string[] | null>Native "Choose folders to import" dialog (multiple folders). Used by the Choose folders… button in asset import.
getAssistantRuntimeStatus()Promise<{ codexCliInstalled, codexCliPath, codexCommand, message }>Result of the Codex CLI lookup

Codex CLI discovery​

  1. GENIE_CODEX_COMMAND. An absolute path must be an executable file; a bare command name is resolved with command -v in a login zsh shell. If the variable is set but nothing is found, discovery stops and the assistant is disabled with the message "GENIE_CODEX_COMMAND is set to …, but that executable was not found."
  2. /opt/homebrew/bin/codex
  3. /usr/local/bin/codex
  4. command -v codex in a login zsh shell (/bin/zsh -lic).

When the CLI is found, its absolute path is passed to the backend, which adds the CLI's folder to PATH for the app-server process.

Behaviour without the Codex CLI​

The app opens normally with AI features disabled. Projects, the genome browser, assets and image export work. The chat shows "AI Features Unavailable" and "Install the Codex CLI to use assistant chats on this desktop app.", the composer placeholder reads "AI features unavailable", and a notice above the composer reads "Codex CLI is not installed. AI features are unavailable until you install the Codex CLI."

Packaging​

CommandOutput
pnpm desktop:devBuilds the renderer, prepares resources and runs Electron from source
pnpm desktop:packSame build, then electron-builder --dir: an unpacked Genie.app in apps/desktop/dist/
pnpm desktop:distSame build, then electron-builder --mac dmg zip in apps/desktop/dist/

Builds are unsigned (mac.identity is null and CSC_IDENTITY_AUTO_DISCOVERY=false), and the app is not packed into an asar archive. electron-builder's cache is kept in genie/.cache/electron-builder.

The packaged app carries two resource folders:

Folder in Genie.app/Contents/Resources/SourceUsed as
renderer/apps/standalone/.next-desktop (from pnpm build:desktop-renderer)The backend's static root
backend-resources/apps/desktop/.generated/backend-resources (from scripts/prepare-backend-resources.mjs)The backend's resource root. Holds copies of the standard track registration sources from packages/genome-browser.

When running from source (pnpm desktop:dev), the renderer root is apps/standalone/.next-desktop and the resource root is the genie/ folder.

Logs​

Startup messages are appended to /tmp/genie-desktop-runtime.log, or to the file named by GENIE_DESKTOP_LOG_PATH: bootstrap start, the backend start options, the backend URL, startup errors, uncaught exceptions and unhandled rejections. Per-chat app-server logs are written to each session folder as app-server.log (see Backend reference).

Cloud deployment (AWS CDK)​

The cloud web build is a static frontend with a serverless project API. It has no assistant. The CDK app is infra/aws-cdk (@genie/aws-cdk); the stack class is GenieCloudStack, deployed as GenieDevCloudStack by default.

Stack resources​

ResourceDetails
Cognito user pool<appName>-users, self sign-up, email sign-in, plus a web client. A pre-sign-up trigger (<appName>-pre-sign-up) confirms new accounts and marks their email verified, so no confirmation code is sent
DynamoDB tableOn-demand single table (PK/SK with two global secondary indexes), streams enabled (new and old images), point-in-time recovery
S3 object bucketVersioned, S3-managed encryption. Holds uploaded assets, plugin bundles, results and Studio outputs.
S3 static web bucketPublic access blocked, S3-managed encryption, SSL enforced
CloudFront distributionS3 origin with origin access control; default root object index.html; 403 and 404 answered with /index.html (status 200) so client-side routes work; caching disabled; HTTPS redirect; price class 100
Lambda functionNode.js 22 (lambda/api/index.mjs), with a log group kept for one week
API Gateway HTTP APIA default route that sends every request to the Lambda function
Bucket deploymentUploads apps/standalone/dist (or the webDistPath context value) and invalidates /*
Custom resourceWrites genie-runtime-config.js to the web bucket

Stack outputs: UserPoolId, UserPoolClientId, ProjectTableName, ObjectBucketName, StaticWebBucketName, StaticWebDistributionDomainName, StaticWebUrl and HttpApiUrl.

By default resources are retained when the stack is deleted (removalPolicy context retain).

Building and synthesizing​

pnpm build runs vite build --mode web into apps/standalone/dist, and pnpm cdk:synth runs cdk synth in infra/aws-cdk:

pnpm build
pnpm cdk:synth

cdk:synth only renders the CloudFormation template into infra/aws-cdk/cdk.out; it does not change AWS. Following the team policy above, any deploy command goes into a file for review rather than being run directly.

Runtime configuration​

The custom resource writes this file, so one frontend build can point at any stack:

window.__GENIE_RUNTIME_CONFIG__ = {
"appTarget": "web",
"backendMode": "cloud",
"assistantEnabled": "false",
"authRequired": "true",
"awsRegion": "<region>",
"cloudApiUrl": "<HTTP API URL>",
"userPoolId": "<user pool id>",
"userPoolClientId": "<client id>"
};

See Configuration.

No assistant in the cloud​

The Lambda project API serves projects, assets, analysis state, Studio outputs, browser state, results and sharing. It does not run the Codex CLI. Its /models route answers 404 with "Assistant models are not available from the cloud project API." With assistantEnabled false, the frontend hides chat and mode-switch actions, analysis and plan routes redirect to Genome mode, and starting an assistant session fails with "Assistant sessions are only available in the desktop app."

Sign-in​

When authRequired is true, visitors do not need an account. The first visit starts a guest session automatically: the API creates a Cognito user with a generated guest-…@guest.genie.invalid email and a random password (POST /auth/guest), and the browser keeps those guest credentials in local storage so the same guest returns on the next visit. Everything a guest creates is owned by that user's Cognito sub, exactly like a normal account.

The account button on the right of the header shows Guest with Create account…, or the account email once converted. The web app has no sign-in or sign-out, so a browser's work is never swapped out for another account's:

ActionWhat happens
Create account…POST /auth/convert-guest with the guest's token. The API checks that the email is free and the password meets the pool policy (8+ characters, upper and lower case, a number and a symbol), then sets the real email (marked verified, so no code is sent) and password on the same Cognito user. The sub does not change, so every guest project now belongs to the account.

Guest work is protected in several ways:

  • A saved guest is only replaced if Cognito reports it no longer exists; on any other error the app shows Try again, and replaced guest credentials are kept in a local archive (genie.cloudGuestArchive.v1).
  • Guest creation is serialised across tabs with the Web Locks API, so two tabs opened at once share one guest.
  • If a conversion fails half way, the API restores the guest email so the saved guest credentials keep working.

Sessions are renewed in the background with Cognito refresh tokens (POST /auth/refresh, valid for 10 years), and a request that comes back 401 renews the session once and retries. /auth/sign-in and /auth/sign-up are unchanged; sign-up refuses the reserved guest domain. Routes under /shared/ skip the session entirely so share links open for anyone.

Sharing​

Sharing is implemented by the cloud project API (the Lambda function and genie-local-cloud); the local backend does not implement it.

RoutePurpose
GET /projects/:projectId/sharingRead the owner's general-access setting
PUT /projects/:projectId/sharingSwitch between restricted and anyone-with-link
GET /shares/:shareIdAnonymous, view-only project payload
Shared asset, plugin and result routesExpose only files referenced by an active share

In the app, Share opens a dialog with General access ("Private" or "Anyone with the link"), the role "Viewer" and Copy link. The link opens /shared/:shareId, a read-only browser with a "View only" badge; changes made there are not saved. Share records live in the same DynamoDB table, and the grant can be revoked by switching back to "Private".

Local cloud simulator​

genie-local-cloud is a separate Node server, checked out next to genie/ (it is not part of the monorepo). It implements the same project REST contract as the Lambda function so cloud mode can be developed without AWS.

PropertyValue
Startpnpm dev:local-cloud from genie/ (or pnpm -C genie-local-cloud dev from the parent folder)
URLhttp://localhost:8790 (PORT or GENIE_LOCAL_CLOUD_PORT; host HOST or GENIE_LOCAL_CLOUD_HOST)
Storage.local-cloud/dynamodb/single-table.json (DynamoDB-shaped items with PK and SK) and .local-cloud/s3/objects/ (S3-style keys); override the root with GENIE_LOCAL_CLOUD_DATA_ROOT
AuthNot verified; requests without a bearer token act as one local user
ScopeProject data and sharing only; no chat or model APIs
LatencyGENIE_LOCAL_CLOUD_LATENCY_MS adds a fixed delay to every non-preflight request; GENIE_LOCAL_CLOUD_LATENCY_JITTER_MS adds a random 0 to N ms on top

pnpm dev:local-cloud:latency runs it with 800 ms latency and 400 ms jitter, and pnpm dev:web-cloud runs that together with pnpm dev:web. In web mode the Vite dev server proxies /api/cloud to http://localhost:8790, so the frontend uses /api/cloud as its project API.

The local backend can also mirror projects and browser state to a cloud project API, and read the browser state back before genome tools run, when it is started with GENIE_CLOUD_API_URL (for example http://localhost:8790 for the simulator).