Chorus is a Discord bot — the "party rock commander" for Compoverse, ThaSauce's music composition community. It runs listening parties: fetches a competition round's entries, transcodes them, generates AI voice announcements via AWS Polly, and streams the whole show out over Icecast, all coordinated through a handful of Discord slash commands.
TypeScript (ESM, nodenext) · Node 24 · @sapphire/framework v5 · discord.js v14 · XState v5 · nodeshout (Icecast) · AWS Polly · Vitest
Prereqs:
- mise — pins Node 24 and Yarn 4, manages env defaults, and runs project tasks. Install per the mise docs and add
eval "$(mise activate zsh)"(or your shell's equivalent) to your shell rc so mise auto-loads oncdinto the repo. - fnox — pulls secrets from 1Password and caches them locally age-encrypted. See "Environment" below for the full setup.
mise install # installs Node + Yarn
yarn install
op signin # see "Environment" below for prereqs
mise run secrets:sync # populates fnox.local.toml from 1Password
mise run dev # tsx watch src/index.ts — live reloadFor a production-like run:
mise run build
mise run startBoth yarn <script> and mise run <task> work for project commands. Node and Yarn versions are pinned by mise.toml; package.json's engines.node and packageManager fields exist for Heroku.
Secrets live in 1Password and are cached locally via fnox; non-secret defaults (AWS region, Icecast host, etc.) live in mise.toml's [env] block. The bot reads everything straight from process.env.
Prereqs:
- 1Password account, with a
Developmentvault containing an item namedchoruswhose fields hold the secrets listed infnox.toml(field names are underscored, lowercased env-var names — e.g.,hubot_discord_token). - 1Password CLI:
brew install 1password-cli. - 1Password 8 desktop app with CLI integration enabled (Settings → Developer).
op account addonce, soop signinworks.- fnox:
brew install fnox. - In
.zshrc:eval "$(fnox activate zsh)" export FNOX_AGE_KEY_FILE="$HOME/.config/fnox/age.txt"
- An age private key at
~/.config/fnox/age.txt(age-keygen -o ~/.config/fnox/age.txt; back up the private key in your personal 1Password vault). Add the public key tofnox.toml'srecipientsarray.
First-time clone:
op signin # Touch ID prompt
mise run secrets:sync # populates fnox.local.toml
mise run devAfter secrets:sync everything reads from the local age cache — no 1Password calls per run. Re-run op signin then mise run secrets:sync whenever a secret rotates in 1Password.
Required secrets (fields on the chorus 1Password item):
hubot_discord_token— bot token from the Discord developer portalguild_id— Discord guild the slash commands register against (Chorus is guild-only, not global)debug_channel_id— channel for the bot's debug log streamaws_access_key_id,aws_secret_access_key— AWS credentials for Polly TTShubot_stream_source_password— Icecast source-client password
Non-secret defaults (AWS_REGION, HUBOT_STREAM_HOST, HUBOT_STREAM_PORT, HUBOT_STREAM_MOUNT, AWS_NODEJS_CONNECTION_REUSE_ENABLED) are set in mise.toml's [env] block.
yarn test # run once
yarn test:watch # watch mode
yarn test:coverage # with v8 coverageTests live under test/ and run through the real Sapphire listener pipeline. See CLAUDE.md for details.
yarn lint # oxlint + oxfmt --check
yarn lint:fix # auto-fix
yarn build # also serves as a typecheck (tsc with emit)CI (.github/workflows/ci.yml) runs lint + build + test on push and PR.
Deployed to Heroku. The Node buildpack runs yarn build during the build phase (no pre-built dist/ is committed), and the Procfile runs yarn start at dyno boot to execute the compiled dist/index.js.
See CLAUDE.md for a tour of the state-machine layout, audio pipeline, and testing conventions.