tools
| Script | What |
|---|---|
make-cat.py | Writes the ginger cat's twelve SVGs from the SVG set it is given. |
make-sounds.py | Synthesises the poke and dizzy sound effects as WAV files. |
install-android.sh | Installs the debug APK on a connected device; configured by environment variables, see its header. |
release-apks.sh | Builds the three release APKs of one calendar version into ~/whiskers-release/<version>/ (see its header). |
run-whiskersd.sh, preflight.sh | Start the service and check everything the app talks to; both take their addresses from the environment or an argument. |
android-sdk.sh, android-gradle.sh | Install the SDK packages at the levels in mise.toml; run Gradle in android/ with the optional -Pwhiskers.* taken from WHISKERS_* variables. Neither assumes mise or nix: they read ANDROID_HOME and ANDROID_NDK_HOME and say what is missing. |
with-secrets.sh | Runs a command under fnox exec when fnox is set up, plainly otherwise. |
check-versions.sh | Fails when a version written outside mise.toml (the wasm-bindgen pins, the Gradle build's SDK levels, hk.pkl) differs from it. |
check-private.sh | Fails if a term from your gitignored .private-terms appears in a tracked file. --message FILE checks a commit message; --push checks every commit that is on no remote yet (files, names, messages), because the whole history is public. |
build-core.sh | Builds the Rust core for both Android ABIs, generates the Kotlin bindings and writes the grey cat's drawing (gen-kotlin); Gradle runs it before every build. |
Keeping the repository free of private details
Copy nothing personal into a tracked file. To make that checkable, put the terms that must never
appear (a child's or family member's name, a hostname, an IP address, a device serial, a user
name, a personal path) one per line in .private-terms at the repository root. That file is
gitignored and must stay out of git: the terms are exactly what the repository must not contain.
tools/check-private.sh greps every tracked file for them (case-insensitive, fixed strings; blank
lines and # comments are ignored) and prints file:line for each hit, exiting non-zero. Without
the file it says so and passes, because there is nothing to check against; WHISKERS_REQUIRE_PRIVATE_TERMS=1
makes a missing file an error. tools/preflight.sh runs it, and so does a Rust test
(whiskers-core's tests/private_terms.rs), so cargo test catches a regression. It skips the vendored third-party/pixel-icons and the icon allowlist, which have to name upstream's icons (a short term matches inside ordinary words there).
Running them
Every script has a mise task; mise tasks lists them, mise run <task> runs one, and mise run <task> -- <arguments> passes arguments on. The scripts themselves take everything from the environment and work in any shell that has the tools of mise.toml on PATH, so they also work inside nix develop or a toolchain you installed yourself.
| Task | Script or command |
|---|---|
core | build-core.sh |
android:debug, android:lite, android:test, android:record-goldens, android:verify-goldens | android-gradle.sh with the Gradle task |
android:install, android:inspect, android:fetch-voices, release:apks | install-android.sh, inspect-apk.sh, the two fetch-*.sh, release-apks.sh |
android:sdk | android-sdk.sh |
whiskersd, whiskersd:service, preflight | cargo run, run-whiskersd.sh, preflight.sh |
check, check-private, check-versions, check-wasm, clippy, test | the fast gates |
dev | the dev daemons (whiskersd and the website) |
Git hooks (hk)
mise run hooks:install installs the hooks in hk.pkl: before a commit check-private.sh and cargo clippy (warnings are shown, an error-level lint stops it), on the commit message check-private.sh --message, before a push check-private.sh --push (every unpushed commit, not just the tip) and the website's tests. The tree is not formatted by rustfmt, so formatting is not checked.
Dev daemons (pitchfork, through mise)
[daemons] in mise.toml declares whiskersd and the website's wrangler dev; mise starts them under pitchfork. mise run dev starts both and waits until they answer; mise daemons status, mise daemons logs and mise daemons stop manage them. whiskersd listens on WHISKERS_ADDR (default 127.0.0.1:47900) and needs WHISKERS_MODEL_URL.
Secrets (fnox, optional)
fnox.toml declares TYPESAFE_API_KEY, ELEVENLABS_API_KEY, ELEVENLABS_VOICE_ID and WHISKERS_MODEL_URL with no value and no provider. Without fnox, export them in the environment before mise run whiskersd. To use fnox, put a provider of your own, and the secrets it should resolve, in fnox.local.toml (gitignored; a secret defined there replaces the declaration in fnox.toml) or in ~/.config/fnox/config.toml. With 1Password the values stay in the vault and the file holds references:
[providers.op]
type = "1password"
vault = "<your vault>"
[secrets.TYPESAFE_API_KEY]
provider = "op"
value = "<item>/<field>"
With age the values are stored encrypted for your public key (age-keygen makes the pair):
[providers.age]
type = "age"
recipients = ["age1..."]
# then: fnox set TYPESAFE_API_KEY --provider age
tools/with-secrets.sh runs the task's command under fnox exec when fnox check passes, and plainly otherwise (set WHISKERS_NO_FNOX=1 to force plain). The provider fields are at https://fnox.jdx.dev.
With nix (optional)
nix develop gives a shell with mise and the native tools a downloaded toolchain needs (compiler, pkg-config, OpenSSL, curl, unzip, bzip2); it puts what mise install has installed on PATH. The shells web, backend, android and android-emu are the same shell. Downloaded programs (the NDK, build tools) are ordinary dynamically linked binaries: NixOS with nix-ld runs them as they are; without nix-ld use nix develop .#fhs. The flake also builds the service (packages.whiskersd, whiskersd-static) and celld; it holds no tool version.