The toolchain, the environment and the tasks of this repository: the one place every tool version lives. Nothing here needs nix, nix-darwin or root:
mise trust && mise install the toolchain (a C compiler and git must exist)
mise tasks what can be run
mise run check the fast gates
mise run android:debug the APK
tools/check-versions.sh (part of mise run check) fails when a version
written elsewhere (Cargo.toml's wasm-bindgen pin, the Gradle build's SDK
levels, hk.pkl's hk release) differs from the one here. The nix flake only
wraps this file; it holds no version of its own.
13min_version = "2026.10.0"
15[settings]
[daemons] below needs it.
17experimental = true
19[tools]
The Rust core, the service and the wasm Workers. Android targets for the app, wasm for the website and the backend Worker.
The wasm-bindgen CLI must equal the wasm-bindgen crate pinned in web/Cargo.toml and
backend/worker/Cargo.toml (checked by tools/check-versions.sh).
26"cargo:wasm-bindgen-cli" = "0.2.127"
worker-build links OpenSSL on Linux. A system copy (and pkg-config) is not assumed: the same OpenSSL from conda-forge is installed first and linked from where mise put it.
29"conda:openssl" = "3.5.9" 30"cargo:worker-build" = { version = "0.8.6", depends = ["conda:openssl"], install_env = { OPENSSL_DIR = "{{ env.MISE_DATA_DIR | default(value=xdg_data_home ~ '/mise') }}/installs/conda-openssl/3.5.9", RUSTFLAGS = "-C link-arg=-Wl,-rpath,{{ env.MISE_DATA_DIR | default(value=xdg_data_home ~ '/mise') }}/installs/conda-openssl/3.5.9/lib" } }
The Android app. No Gradle wrapper by design: the version is here.
The SDK's command-line tools; the platform, build tools and NDK are installed by
mise run android:sdk at the levels in [vars].
37android-sdk = "13.0"
celld, the self-hosted Durable Objects host, for the backend Worker.
The jdx tools: git hooks, supervised dev daemons, secrets, CLI specs.
54[vars]
Android SDK levels. build.gradle.kts must agree (tools/check-versions.sh).
Only for mise run android:emulator.
60android_emulator_image = "system-images;android-35;google_apis;x86_64"
62[env]
Where the SDK and NDK are (the android-sdk tool sets ANDROID_HOME to its own directory; tasks put the SDK packages there). Scripts read these from the environment and never assume mise or nix; export them yourself if you bring your own SDK.
hk runs its hooks through mise, so the hook finds hk, pkl and cargo whatever the shell of the commit.
69HK_MISE = "1"
What the tasks read, all optional. Secrets are never put in this file: whiskersd's keys come from the environment, or from fnox (see fnox.toml and tools/README.md). WHISKERS_ADDR address whiskersd listens on (private network address, not 0.0.0.0) WHISKERS_MODEL_URL the model gateway whiskersd proxies to (required by whiskersd) WHISKERS_HOST default service address baked into an APK (-Pwhiskers.host) WHISKERS_VERSION_NAME / WHISKERS_VERSION_CODE / WHISKERS_APPLICATION_ID / WHISKERS_ABIS the other -Pwhiskers.* properties (see android/README.md) WHISKERS_KEYSTORE, WHISKERS_KEYSTORE_PASSWORD, WHISKERS_KEY_ALIAS, WHISKERS_KEY_PASSWORD sign with your own key (all four or none) WHISKERS_SERVICE address preflight checks (host:port) ADB, ANDROID_SERIAL, WHISKERS_ANDROID_USER see tools/install-android.sh
82WHISKERS_ADDR = { value = "127.0.0.1:47900" }
---------------------------------------------------------------- setup
90[tasks."hooks:install"] 91description = "Install the git hooks (hk.pkl): check-private and clippy before a commit, the web tests before a push" 92run = "hk install" 93 94[tasks."android:sdk"] 95description = "Install the Android platform, build tools, platform tools and NDK at the levels in [vars]" 96run = "tools/android-sdk.sh" 97env = { WHISKERS_ANDROID_PLATFORM = "{{ vars.android_platform }}", WHISKERS_ANDROID_BUILD_TOOLS = "{{ vars.android_build_tools }}", WHISKERS_ANDROID_NDK = "{{ vars.android_ndk }}" } 98 99[tasks."android:emulator"] 100description = "Also install the emulator and an x86_64 system image (large)" 101depends = ["android:sdk"] 102run = 'yes | sdkmanager --install emulator "{{ vars.android_emulator_image }}"'
---------------------------------------------------------------- checks
110[tasks."check-versions"] 111description = "Fail if a version written outside mise.toml differs from it" 112run = "tools/check-versions.sh" 113 114[tasks."check-wasm"] 115description = "The portable crates (ports, service, conformance) still build for wasm32 (a Worker)" 116run = "cargo check --target wasm32-unknown-unknown -p whiskers-ports -p whiskers-service -p whiskers-conformance" 117 118[tasks.clippy] 119description = "cargo clippy over the core workspace (an error-level lint fails; warnings are shown)" 120run = "cargo clippy --workspace" 121 122[tasks."test:core"] 123description = "The Rust workspace's tests" 124run = "cargo test --workspace" 125 126[tasks."test:web"] 127description = "The website's tests (its own workspace, natively)" 128run = "cargo test --workspace --manifest-path web/Cargo.toml" 129 130[tasks."test:backend"] 131description = "The backend Worker's pure tests, natively" 132dir = "backend/worker" 133run = "cargo test" 134 135[tasks.test] 136description = "All native tests: the core and the website" 137depends = ["test:core", "test:web"] 138 139[tasks.check] 140description = "The fast gates: private terms, versions, wasm build, clippy, core and website tests" 141depends = ["check-private", "check-versions", "check-wasm", "clippy", "test"] 142 143[tasks.preflight] 144description = "Check everything the app talks to (WHISKERS_SERVICE=host:port or pass it as an argument)" 145run = "tools/preflight.sh"
---------------------------------------------------------------- android
154[tasks."android:debug"] 155description = "Build the debug APK (optional -Pwhiskers.* from the WHISKERS_* variables)" 156depends = ["android:sdk"] 157run = "tools/android-gradle.sh :app:assembleDebug" 158 159[tasks."android:lite"] 160description = "Build the debug APK without the offline voice (about 90 MB smaller)" 161depends = ["android:sdk"] 162run = "tools/android-gradle.sh :app:assembleDebug -Pwhiskers.offlineVoice=false" 163 164[tasks."android:test"] 165description = "The app's JVM unit tests" 166depends = ["android:sdk"] 167run = "tools/android-gradle.sh :app:testDebugUnitTest" 168 169[tasks."android:record-goldens"] 170description = "Rewrite the screenshot goldens (look at the PNGs afterwards)" 171depends = ["android:sdk"] 172run = "tools/android-gradle.sh :app:recordRoborazziDebug"
record and verify in one Gradle invocation race, so they are separate tasks.
180[tasks."android:fetch-voices"] 181description = "Fetch the offline speech model and the offline neural voice (Gradle does this before a build)" 182run = ["tools/fetch-speech-model.sh", "tools/fetch-offline-voice.sh"] 183 184[tasks."android:install"] 185description = "Install the debug APK on a connected device and start it" 186depends = ["android:sdk"] 187run = "tools/install-android.sh" 188 189[tasks."android:inspect"] 190description = "Print who signed an APK, what it asks for and which ABIs it carries" 191depends = ["android:sdk"] 192run = "tools/inspect-apk.sh" 193 194[tasks."release:apks"] 195description = "Build the three release APKs into ~/whiskers-release/<version>/ (arguments: version code)" 196depends = ["android:sdk"] 197run = "tools/release-apks.sh"
---------------------------------------------------------------- the service
---------------------------------------------------------------- the website
---------------------------------------------------------------- the backend Worker
231[tasks."backend:celld"] 232description = "The backend Worker on celld, self-hosted, on port 8801 (state in backend/worker/.celld/dev)" 233dir = "backend/worker" 234run = "celld dev . --port 8801" 235 236[tasks."backend:wrangler"] 237description = "The backend Worker under wrangler dev --local on port 8802" 238dir = "backend/worker" 239run = "wrangler dev --local --port 8802 --persist-to .wrangler/state" 240 241[tasks."backend:host"] 242description = "Start, stop or restart one backend host: native, wrangler or celld (arguments: host action)" 243run = "backend/worker/tools/host" 244 245[tasks."backend:probe"] 246description = "Show what each backend host says to a few odd requests (argument: native, wrangler or celld)" 247run = "backend/worker/tools/probe" 248 249[tasks."backend:crosscheck"] 250description = "Run the whole comparison of the three backend hosts (see backend/README.md)" 251run = "backend/worker/tools/crosscheck"
---------------------------------------------------------------- daemons
Supervised by pitchfork, started and stopped through mise. mise run dev starts both and
waits until they answer; mise daemons status, mise daemons logs, mise daemons stop.
262[daemons.site] 263run = "cd web/worker && exec wrangler dev --local --port 8788" 264ready_http = "http://127.0.0.1:8788/" 265 266[tasks.dev] 267description = "Start the dev daemons (whiskersd and the website) and wait until they answer" 268daemons = true 269run = "echo 'whiskersd on '\"$WHISKERS_ADDR\"', the website on http://localhost:8788/; mise daemons stop to stop them'"