Development setup
This page covers working on the frogg monorepo itself. Commands run from the repository root unless stated otherwise.
Prerequisites
Section titled “Prerequisites”- Node.js 22. Root
package.jsonrequires>=22;.tool-versionspinsnodejs 22.20.0. CI uses Node 22. - npm. The repo uses npm workspaces (
packages/*,apps/*) and a committedpackage-lock.json. - git. Dev scripts use
git rev-parse --show-toplevelto find the checkout root. - bash for the dev scripts on macOS and Linux. On Windows use
npm run dev:win(PowerShell).
.tool-versions also lists rust, java and android-sdk. Rust is only needed for the
inactive reference sources in apps/desktop and apps/daemon-rs. Java and the Android SDK
are needed for Android builds, see Platform builds.
Install
Section titled “Install”npm ciInstall runs two hooks:
postinstallappliespatches/*.patch(scripts/ci/postinstall-patches.mjs) and runsnpm run brand:prepare, which writes the selected brand to.generated/branding/.prepareinstalls the lefthook git hooks. It is skipped whenLEFTHOOK=0orCI=true.
A new git worktree has no node_modules. Run npm ci in it before anything else, or the
pre-commit hook fails with oxfmt: not found.
Build order
Section titled “Build order”Workspaces import each other’s built dist/ output, not sibling src/ files. npm does not
order the builds for you. The dependency chain is:
protocol → client → highlight, plugin, relay → server → cliUse the root scripts. They encode the order.
| Script | Builds |
|---|---|
npm run build:protocol |
@frogg/protocol |
npm run build:client |
@frogg/protocol, then @frogg/client |
npm run build:server-deps |
client chain, then highlight, plugin and relay in parallel |
npm run build:server |
server deps, then @frogg/server and @frogg/cli |
npm run build:app-deps |
highlight, client, plugin and @frogg/expo-two-way-audio |
npm run build:ui |
Expo web export of apps/ui (runs build:app-deps first) |
npm run build:desktop |
web UI, then the Electron app-only shell |
npm run build:daemon-web-ui |
copies the web export into packages/server/dist/server/web-ui |
Most build:* scripts have a :clean variant, for example npm run build:server:clean,
which removes stale dist/ first.
Which build to run after a change:
packages/protocolorpackages/client:npm run build:client.packages/server,apps/cli,packages/relayorpackages/highlight:npm run build:server.- Anything
apps/uiconsumes:npm run build:app-deps.
Run the dev stack
Section titled “Run the dev stack”Run the daemon and the client in separate terminals.
npm run dev:server # daemon on 0.0.0.0:6768npm run dev:app # Expo (Metro) on port 8081, pointed at the dev daemondev:serverrunsbuild:server-depsonce, then watches protocol, client and server withconcurrently. If the deps are already built, skip that step withFROGG_SKIP_DEV_SERVER_BUILD=1 npm run dev:server.dev:appsetsEXPO_PUBLIC_LOCAL_DAEMONto the dev daemon endpoint and passes the current git branch asEXPO_PUBLIC_FROGG_DEV_BUILD_LABEL.EXPO_PUBLIC_*values are inlined at bundle time. Restart Metro after changing them.npm run devis an alias fordev:server.npm run dev:desktopruns the Electron shell against a web dev server on port18000 + <offset derived from the checkout path>(override withFROGG_ELECTRON_UI_PORT). State lives in.dev/electron/. The shell is app-only: it never starts a daemon. Run a daemon yourself, for exampledev:server, and add it as a host in the app.npm run dev:winrunsscripts/dev/dev.ps1. It builds server and app deps, then runs the daemon watcher and Metro together in one PowerShell terminal.
The dev daemon and Expo bind 0.0.0.0 so they are reachable from other machines on the LAN.
Ports 6768 and 8081 are the defaults for the official brand. A custom brand selected with
FROGG_BRAND_DIR uses its daemonPort and daemonPort + 1 instead. Port 9999 is the default
port of a separately installed daemon; keep it free of dev work.
Dev state
Section titled “Dev state”FROGG_HOME holds agents, projects, worktrees, config.json and daemon.log. The dev scripts
default it to .dev/frogg-home inside the current checkout, so dev work never touches ~/.frogg.
FROGG_HOME=~/.frogg-blue npm run dev:server # explicit homeFROGG_DEV_SEED_HOME=~/.frogg npm run dev:server # copy JSON metadata from another homeFROGG_DEV_SEED_HOME=~/.frogg FROGG_DEV_RESET_HOME=1 npm run dev:server # clear, then reseedSeeding copies agents/**/*.json, projects/**/*.json and config.json. It skips a home that
already has data unless FROGG_DEV_RESET_HOME=1 is set.
Run the CLI from source
Section titled “Run the CLI from source”npm run cli -- ls -a -gnpm run cli -- daemon statusnpm run cli runs apps/cli/src/index.ts with tsx through scripts/dev/dev-home.sh. It uses
the checkout’s .dev/frogg-home and the dev daemon endpoint. An installed frogg binary is a
different build; use npm run cli to test CLI changes.
Several checkouts at once
Section titled “Several checkouts at once”Each checkout or worktree gets its own .dev/frogg-home automatically. Give each one its own
ports:
FROGG_LISTEN=0.0.0.0:6868 npm run dev:serverFROGG_LISTEN=0.0.0.0:6868 EXPO_PORT=8181 npm run dev:appdev:app derives the daemon endpoint from FROGG_LISTEN. Set FROGG_DEV_DAEMON_ENDPOINT to point
it somewhere else. npm run worktree:status lists what every worktree has committed and left
uncommitted.
Working on frogg inside frogg
Section titled “Working on frogg inside frogg”The repo’s own frogg.json configures worktrees created by a frogg daemon. See
Project config for the format.
worktree.setup runs, in order: npm ci, scripts/mobile/seed-ios-native-cache.mjs,
scripts/dev/seed-worktree-dev-state.mjs (copies the source checkout’s .dev/frogg-home and
packages/server/.env), npm run build:server, and the @frogg/expo-two-way-audio build.
| Script | Type | What it runs |
|---|---|---|
daemon |
service | scripts/dev/dev-daemon.sh on FROGG_PORT, skipping the deps build |
app |
service | scripts/dev/dev-app.sh with Metro on FROGG_PORT, pointed at the daemon service |
desktop |
service | ./packages/desktop/scripts/dev.sh |
ios-simulator |
service | scripts/mobile/frogg-ios-simulator-service.sh (macOS only) |
typecheck |
script | npm run typecheck |
Check a change
Section titled “Check a change”-
Run the checks your diff can break:
Terminal window node scripts/ci/verify.mjs --changed # format, lint, typecheck, unit testsnode scripts/ci/verify.mjs --changed --fast # same, without tests--changedcompares against the merge base withorigin/main(ormain). Use--changed=<ref>for another base. It formats and lints only changed files, and typechecks and unit-tests only the workspaces those files belong to. It addsnpm run test:scriptswhenscripts/ci/orscripts/release/changed. -
Before merging, run the full typecheck:
Terminal window npm run typecheck -
For a local stand-in for CI, run everything in parallel:
Terminal window npm run verify # format, lint, typecheck, script tests, unit testsnpm run verify -- --fast # format, lint, typecheck only
The individual gates are npm run format:check (oxfmt), npm run lint (oxlint),
npm run typecheck and npm run knip (unused files and exports; not run by CI).
Git hooks
Section titled “Git hooks”lefthook.yml runs three pre-commit jobs in parallel on staged files: an oxfmt check, oxlint,
and scripts/ci/typecheck-staged.mjs, which typechecks only the workspaces that contain staged
sources. Fix formatting with npm run format.
Typecheck gotchas
Section titled “Typecheck gotchas”- On a fresh checkout
npm run typecheckfails:@frogg/appcannot resolve@frogg/clientand@frogg/clicannot resolve@frogg/server. Runnpm run build:serverandnpm run build:app-depsonce, then typecheck. CI does the same. - A downstream error that says a field you just added to
packages/protocoldoes not exist meansdist/is stale. Runnpm run build:clientbefore you debug the error. pretypecheckrunsbrand:prepareand builds@frogg/expo-two-way-audio.npm run typecheck:serverchecks relay, protocol, client, server and cli only. It is the fastest gate for daemon and protocol work.
Platform builds
Section titled “Platform builds”These are the prerequisites the build scripts and workflows require. Treat anything not listed as unvalidated.
Desktop (Electron). Install the Electron binary, then build for one target:
npm run install:electron --workspace=@frogg/desktop-electronnpm run build:desktop -- --target linux-x64 # linux|darwin|win - x64|arm64Output goes to apps/desktop-electron/release/. macOS artifacts must be built on macOS
(build-electron.mjs refuses otherwise). Windows signing uses Azure Trusted Signing only when
TRUSTED_SIGNING_ACCOUNT, TRUSTED_SIGNING_ENDPOINT and TRUSTED_SIGNING_PROFILE are set.
macOS builds are not notarized.
Android. Needs Java (CI uses Temurin 21) and an Android SDK in ANDROID_HOME or
ANDROID_SDK_ROOT.
node scripts/release/build-android-apk.mjs --abi arm64-v8a --serial --out-dir release-assetsnpm run android --workspace=@frogg/app # dev client via expo run:android--serial limits Gradle to one worker for machines with about 8 GB of RAM. Without the
FROGG_ANDROID_KEYSTORE* variables the APK is debug-signed; see
Release process.
iOS. macOS only. Needs Xcode with an iOS Simulator runtime. npm run ios --workspace=@frogg/app
builds and launches the dev client with expo run:ios. CI builds an unsigned simulator app in
branding.yml. frogg does not publish iOS builds.
Next: Architecture and Testing.