Skip to content

Create a brand

A brand is a directory with a brand.json manifest and its artwork. Every branding command reads the brand from, in order:

  1. --brand <directory> on brand:check, brand:prepare and brand:stage,
  2. the FROGG_BRAND_DIR environment variable,
  3. brands/frogg (stock frogg).

Relative paths resolve against the repository root.

Prepare a square icon, PNG or SVG, at least 1024 × 1024 pixels. An SVG needs width="1024" height="1024" (or larger) on its root element. Use a transparent background unless the mark should sit on a solid tile.

Terminal window
npm run brand:init -- \
--dir brands/acme \
--id acme \
--name "Acme Studio" \
--app-id com.acme.studio \
--port 10099 \
--icon ~/Downloads/acme-icon.svg
Brand created at /home/you/acme-studio/brands/acme. Build with FROGG_BRAND_DIR=/home/you/acme-studio/brands/acme

All six flags are required. brand:init copies the icon into the directory as icon.svg or icon.png, writes a minimal manifest, and validates it. It refuses to overwrite an existing directory.

brands/acme/brand.json
{
"schemaVersion": 1,
"id": "acme",
"name": "Acme Studio",
"applicationId": "com.acme.studio",
"daemonPort": 10099,
"assets": {
"icon": "./icon.svg"
}
}

That manifest already builds a working product. Everything else has a default.

  • id: a lowercase slug (acme, acme-studio). It becomes the CLI name, home directory, env prefix, URL scheme and service names unless you override them. Treat it as permanent once you ship: changing it moves users’ state to a new directory.
  • applicationId: reverse DNS with at least three segments, lowercase letters and digits only (com.acme.studio). It is the Android package, macOS bundle ID, Windows install path and browser storage namespace. Also permanent.
  • daemonPort: 1024 to 65535. Pick a port no other frogg-based product uses, so both can run on one machine. Stock frogg uses 9999; the example uses 10099.

Add the optional fields you need. A typical product manifest:

brands/acme/brand.json
{
"schemaVersion": 1,
"id": "acme",
"name": "Acme Studio",
"fullName": "Acme Studio for Engineering",
"publisher": "Acme Corp",
"description": "Acme Studio: run and monitor AI coding agents.",
"applicationId": "com.acme.studio",
"daemonPort": 10099,
"assets": { "icon": "./icon.svg" },
"colors": {
"light": {
"accent": "#6d28d9",
"accentForeground": "#ffffff",
"background": "#faf5ff",
"foreground": "#27143d"
},
"dark": {
"accent": "#c4b5fd",
"accentForeground": "#27143d",
"background": "#20152e",
"foreground": "#faf5ff"
}
},
"links": {
"website": "https://studio.acme.example",
"docs": "https://studio.acme.example/docs",
"support": "https://github.com/acme-corp/frogg/issues"
},
"distribution": {
"repository": "acme-corp/frogg",
"updates": "github-release"
}
}

Use the Rebrand tool to start from an example, upload your logo, preview Workspace, Welcome and Companion pages, and configure your brand. It checks the manifest, reserved identities, logo dimensions and both palettes’ text contrast before exporting a ZIP with brand.json and the uploaded assets. Unzip it into brands/<your-id>/, then continue with the check and build steps below.

Colour pairs must reach a 4.5:1 contrast ratio: accent against accentForeground, and background against foreground, in both appearances. Omit colors to get a neutral grey palette. See the manifest reference for every field.

Keep secrets out of this directory. Everything in brand.json is public: it is embedded in the app, installers and container labels.

Terminal window
npm run brand:check -- --brand brands/acme
Acme Studio: valid (com.acme.studio); updates github-release

Add --json to print the fully resolved brand, including every derived default:

Terminal window
npm run --silent brand:check -- --brand brands/acme --json
{
"id": "acme",
"name": "Acme Studio",
"cliName": "acme",
"desktopBinaryName": "acme-desktop",
"homeDir": ".acme",
"envPrefix": "ACME",
"scheme": "acme",
"serviceName": "acme-daemon",
"launchdLabel": "com.acme.studio-daemon",
"artifactPrefix": "acme",
"storagePrefix": "com.acme.studio:",
"...": "..."
}

brand:check fails on:

  • unknown keys or an unsupported schemaVersion,
  • invalid slugs, application IDs or ports,
  • a missing icon, or one that is not square and at least 1024 pixels,
  • colour pairs under 4.5:1 contrast,
  • updates enabled without distribution.repository,
  • reserved frogg identities (see troubleshooting).
Terminal window
FROGG_BRAND_DIR=brands/acme npm run brand:prepare
Brand prepared: Acme Studio (3f9c1a0b52de)

You rarely run this by hand. npm ci, typecheck and every dev:* and build:* entrypoint prepare the selected brand first. The hash in brackets is the fingerprint of the manifest, artwork, generator, bundled skills, installer templates and app version. An unchanged fingerprint skips asset generation.

Generated output is git-ignored. Never commit it.

Path Contents
.generated/branding/brand.json Fully resolved brand, read by Electron, release scripts and CI
.generated/branding/icons/ icon.ico, icon.icns, PNG sizes for desktop packaging
.generated/branding/scripts/ Branded install.sh, uninstall.sh, install-docker.sh, uninstall-docker.sh
.generated/branding/skills/ Bundled agent skills, renamed to <id>-<skill> for custom brands
.generated/branding/deploy/ compose.json, pair-worker.json, install-worker.json, runtime.sh
.generated/branding/eas.json EAS config with your App Store ID, used by brand:eas
.generated/branding/provenance.json Brand ID, version, source revision and fingerprint
apps/ui/.generated/branding/ Expo icons, splash, favicons, PWA manifest and index.html
packages/branding/src/generated/ The resolved brand compiled into the runtime package

Docker build contexts and Nix derivations cannot read a brand directory outside the source tree. brand:stage copies the manifest and artwork into a self-contained, git-ignored directory and prints its path:

Terminal window
FROGG_BRAND_DIR=../acme-brand npm run --silent brand:stage
.branding-input/acme

The Docker and release scripts call this for you. Stock frogg prints brands/frogg.

Select the brand for the whole shell session, then use the normal dev commands:

Terminal window
export FROGG_BRAND_DIR=brands/acme
npm run dev:server # terminal 1: branded daemon
npm run dev:app # terminal 2: Expo web and mobile dev server
npm run dev:desktop # terminal 3 (optional): Electron shell

The dev scripts go through scripts/dev/branded-run.mts, which prepares the brand and holds it for the life of the process.

Setting Stock frogg Custom brand
Daemon listen 0.0.0.0:6768 0.0.0.0:<daemonPort> (Acme: 10099)
Metro / Expo 8081 daemonPort + 1 (Acme: 10100)
Dev state .dev/frogg-home .dev/<id>-home (Acme: .dev/acme-home)

Override with FROGG_LISTEN, EXPO_PORT or <PREFIX>_HOME (ACME_HOME) when you run several checkouts of the same product.

Terminal window
export FROGG_BRAND_DIR=brands/acme
npm run build:ui # web export in apps/ui/dist
npm run build:desktop -- --target linux-x64 # Electron packages in apps/desktop-electron/release
npm run build:server && npm run build:daemon-web-ui
npm run build:daemon-bundle -- --target linux-x64 --out-dir dist/bundles

Desktop targets are linux-x64, linux-arm64, win-x64, win-arm64, darwin-x64 and darwin-arm64. macOS packages must be built on macOS. Daemon bundle targets are the same six; the daemon bundle includes Node, the daemon, the web UI and a CLI launcher named after your cliName.

Next: build and release from GitHub Actions.