a.skh.am / tools

Tools

Command-line tools, two web apps, a Chrome extension and the libraries behind them: a synthesizer driven by a text score, speech-to-text and text-to-speech across machines, a handwriting canvas for the terminal, a keyboard deck with a light under every key, and tools for shipping Android apps and pull requests. Each has a page explaining how it works.

Index

Sound

Speech

Terminal & input

Agents

Release

How they ship

Every Rust tool is a Nix package. The root flake composes each project's own flake, and nix build .#tools joins every binary into one bundle, each exactly once, so a machine installs all of them declaratively and upgrades them by moving one pin. The three speech daemons ship as Home Manager/NixOS modules that run them as user services. Two tools are web apps: Drums is plain ES modules, and Chirp compiles its whole engine to WebAssembly. The Chrome extension loads unpacked, and LLMbience runs from its launcher on a GPU host.

How the tools ship Sources on the left, channels in the middle, destinations on the right. The cli workspace (the speech tools and their shared crates) and the standalone Rust projects (chirp, cursive, gfxsh, ssml, app, triage, keygrid, poppa) go through the root flake.nix, which builds the .#tools bundle with every binary once and exports NixOS modules for the tts, stt and lifetail daemons. Chirp's engine is also compiled to WebAssembly; it and Drums go through the Pages publisher to a.skh.am/tools. The read-aloud extension is loaded unpacked into Chrome, and LLMbience runs from its launcher on a GPU host. cli/ workspacetts · aztts · oaitts · sttazstt · oaistt · lifetail standalone Rustchirp · cursive · gfxsh · ssmlapp · triage · keygrid · poppa drums/ES modules, no build read-aloudChrome extension llmbience/Python, uv flake.nixsubflakes + callPackage chirp-wasmwasm-bindgen + wasm-opt publish-pages.pyconverges gh-pages .#toolsevery binary, once nixosModulestts · stt · lifetail a.skh.am/toolsdrums/ · chirp/ · these pages ChromeLoad unpacked GPU host./llmbience stream chirp
One repository, five ways out. Rust tools leave through one flake, web apps through one Pages publisher.

Surfaces

Most tools are reachable by people and by agents through the same code path. Where a tool speaks MCP, its MCP tools call the same handlers as its CLI, with the same validation and the same receipts, so an agent can do exactly what a person can, and nothing else.

ToolCLITUIDaemonMCPWeb appAndroid appApple appsChrome extensionLibrary / C ABINix module
Chirp
Drums
LLMbience
tts
aztts
oaitts
stt
azstt
oaistt
lifetail
ssml
Read Aloud
Cursive
gfxsh
Keygrid
Triage
meat
Outbox
app
Poppa

The speech crates

The speech tools look like eight binaries, but they are two families over shared crates. tts, aztts and oaitts are three entrypoints into one TTS core that owns the CLI, the daemon, the playback queue and the remote protocol; the provider adapters only encode requests. The STT family mirrors it, and Lifetail, the always-on transcriber, reuses the STT core's realtime sessions. Both cores authenticate to Azure through one broker that takes a key or an Entra token.

The speech crates Crate dependencies in the cli workspace. The tts, aztts and oaitts binaries depend on tts-cli; the stt, azstt and oaistt binaries depend on stt-cli, which depends on both stt-core and tts-cli (for its endpoint, token and remote helpers); lifetail depends on stt-core. tts-cli and stt-core both depend on azure-auth, which authenticates with a key or an Entra token. ttsrouter azttsAzure oaittsOpenAI sttrouter azsttAzure oaisttOpenAI tts-cliCLI · daemon · queueplayback · remote stt-cliaudio in · daemonremote client stt-corebatch + realtime sessions lifetailalways-on azure-authkey or Entra token endpoint · token · remote helpers
Crates, not copies. Frontends pin a provider; routers choose one; everything else is shared.

This site

The Pages branch holds three independent things: the two web apps at drums/ and chirp/, and these pages at the root. One publisher converges all three to the main branch: it records the source revision each part was built from, rebuilds only the parts that are behind, and replaces only the paths each part owns. A Chirp build never touches Drums, and neither touches these pages. The sidebar frames each page, and each app, in a single iframe, but the apps are not wrapped: /tools/chirp/ is the app itself.

How this site is published Three components own disjoint paths on the gh-pages branch. Drums (from drums/) owns drums/; Chirp (from cli/chirp, built to WebAssembly, plus the published chirp-config library) owns chirp/; the site (from site/) owns the root: index.html (the shell), about/ and assets/. A state file, .pages.json, records each component's source revision. The publisher compares it with main, rebuilds only components that are behind, replaces only their paths, and pushes one commit. In the browser the shell frames either a page under about/ or an app, but /tools/chirp/ and /tools/drums/ load the apps directly. drums/source cli/chirp+ chirp-config library site/pages, shell, fonts publish-pages.pybehind main?rebuild, replace, push .pages.jsonrevision per part drums/the app chirp/the app, wasm index.html+ about/ · assets/ the shellsidebar + iframe#/chirp#/chirp/app GH-PAGES serves frames
Three owners, one branch. Each part is rebuilt from its own source paths; the shell only frames them.