oc-sounds
v0.1.2
Published
Cuelume sound feedback for OpenCode V2 agent actions
Readme
oc-sounds
Plugin for OpenCode V2 that makes prompting a more sensual experience. Hear the agent think, read, search, edit, run commands, delegate, and finish with the Cuelume sound palette.
Install
Requires Node.js 22+ and OpenCode V2. Built against
@opencode/[email protected].
Once the first release is published to npm, install globally with:
opencode2 plugin add oc-soundsAll sounds are enabled at 85% volume by default. No individual sound setup is required. You can change the sound for any action in your OpenCode config. The package includes all 17 sound files and needs no browser or sound renderer at runtime.
For project-only installation, add oc-sounds to plugins in your project's
opencode.jsonc instead:
{
"$schema": "https://opencode.ai/config.json",
"plugins": [
{
"package": "oc-sounds",
"options": { "volume": 0.85 }
}
]
}OpenCode watches its configuration for changes. If sounds do not start after installation, restart its background service:
opencode2 service restartUpdate an installed package with:
opencode2 plugin update oc-soundsUntil the first npm release, follow Use a local checkout.
Audio playback
The plugin automatically chooses an installed player:
| Platform | Players, in preference order |
| --- | --- |
| Linux | paplay, pw-play, ffplay |
| macOS | Built-in afplay, then ffplay |
| Windows | ffplay (install FFmpeg and add it to PATH) |
On Debian/Ubuntu, sudo apt install pulseaudio-utils provides paplay.
On Arch, sudo pacman -S libpulse provides it. PipeWire's PulseAudio compatibility
server also supports paplay.
Sound plays on the machine running the OpenCode server. For a local TUI, desktop app, or browser connected to a local server, that is your computer. A remote/headless server needs its own accessible audio output.
Playback runs asynchronously with burst smoothing enabled by default:
- Sound starts are spaced at least 180ms apart per plugin instance.
- Repeated copies of the same sound within 300ms are coalesced, including duplicate cues waiting to play. Five simultaneous commands produce one start cue and one return cue instead of ten overlapping sounds.
- At most three cues wait to play. Older routine cues are dropped when the queue fills, and routine cues older than a second expire.
- Completion, errors, permission requests, questions, and interruptions take priority over queued routine activity. Custom sound mappings retain this priority.
The quiet tails of longer sounds can still overlap. If a player fails, automatic mode tries the next player; if none work, the plugin logs one warning and lets the agent continue. Unloading stops its queued cues, audio processes, and event subscription.
What makes a sound?
Each tool invocation gets a starting cue and a success/error cue, including custom tools, MCP tools, and nested calls that OpenCode exposes through its tool hooks. The built-in tool palette is:
| Tool action | Sound |
| --- | --- |
| Read files | page |
| Glob and grep | release |
| Web search | arrival |
| Web fetch | loading |
| Write and edit | toggle |
| Patch a file | arrival |
| Shell / code execution | tick |
| Delegate to a subagent | arrival |
| Load a skill | sparkle |
| Ask the user a question | chime |
| Other tools | tick |
| Tool succeeds / fails | release / error |
| Web fetch succeeds | ready |
Session actions also have cues. The option keys are listed below:
| Option key | Action | Default |
| --- | --- | --- |
| prompt | Input enters the session inbox | muted |
| start | Agent execution starts | loading |
| step | Model step starts | muted |
| thinking | Reasoning block starts | whisper |
| reply | Model text / progress message starts | scan |
| tool | Fallback tool-start cue | tick |
| toolComplete | Tool completes | release |
| toolError | Tool fails | error |
| question | Question tool starts | chime |
| permission | Permission request appears | bloom |
| permissionReply | Permission request is answered | toggle |
| retry | Model retry is scheduled | scan |
| complete | Agent execution succeeds | success |
| error | Execution or compaction fails | error |
| interrupt | Agent execution is interrupted | droplet |
| compact | Context compaction starts | page |
| compactComplete | Context compaction finishes | ready |
| skill | Skill is activated | sparkle |
| agent | Session agent is switched | arrival |
Reasoning and replies sound once per block, rather than on every streamed token. Tool progress updates do not repeat the start cue. Public events are scoped to the plugin's location and repeated event IDs are deduplicated.
reply covers model text, including mid-task updates such as "I'll check the
files now." OpenCode's text-start event does not distinguish intermediate text
from the final reply, so both use scan. The separate complete cue plays
success when the agent finishes its execution.
OpenCode emits prompt, start, and step almost simultaneously for ordinary
prompts, including before any text is visible. Only start is audible by
default, producing one loading cue rather than a three-sound burst. Set
prompt to "press" or step to "pulse" in cues if you want those extra
lifecycle cues.
Customize
Every action's sound is configurable. Use cues for session actions such as
thinking, replying, and completion, tools for individual tool-start sounds,
and returns for successful per-tool return sounds. Set a value to any Cuelume
sound name, or false to mute it. Omitted settings keep their defaults.
Edit the existing plugin entry in ~/.config/opencode/opencode.jsonc (global)
or your project's opencode.jsonc. Merge it with your other settings. If you use
a local checkout, keep its directory path in package:
{
"$schema": "https://opencode.ai/config.json",
"plugins": [
{
"package": "oc-sounds",
"options": {
"enabled": true,
"volume": 0.85,
"player": "auto",
"playback": {
"minIntervalMs": 180,
"dedupeWindowMs": 300
},
"cues": {
"prompt": "press",
"thinking": "whisper",
"reply": "scan",
"complete": "ready",
"step": false
},
"tools": {
"read": "tick",
"edit": "toggle",
"shell": "pulse",
"webfetch": "loading",
"websearch": "arrival",
"my_mcp_tool": "sparkle"
},
"returns": {
"webfetch": "ready"
}
}
}
]
}This example changes reads to tick, completion to ready, and mutes model-step
cues. Change any name to choose a different sound. For all available action and
tool mappings in one file, see examples/opencode.json,
which is also included in the npm package.
Save your OpenCode config to apply the changes. If the running plugin does not
reload automatically, run opencode2 service restart. Changing these options
does not require rebuilding or reinstalling the plugin.
enabled: defaults totrue. Set tofalseto disable all sounds.volume: from0to1, defaults to0.85(85%). Zero disables playback.player:auto,paplay,pw-play,afplay, orffplay. An explicit player disables automatic fallback to other players.playback.minIntervalMs: minimum time between sound starts, defaults to180.playback.dedupeWindowMs: cooldown for repeating the same sound, defaults to300. Both timing values accept integers from0to5000milliseconds. Set both to0for immediate, unthrottled playback. For a calmer pace, try250and500respectively.cues: override any action in the table, or set it tofalseto mute it. Settingcues.toolreplaces the built-in starting palette for all ordinary tools.questionis configured separately.tools: override starting cues by exact effective tool name or built-in short name (readalso matchesfunctions.read/functions_read). Exact names take precedence.falsemutes that tool's starting cue; completion and error cues are controlled separately. Tool overrides take precedence overcues.tool.returns: override successful return cues by exact effective tool name or built-in short name. The defaultwebfetchreturn isready; other successful tools fall back tocues.toolComplete. Set a value tofalseto mute that tool's successful return without muting its starting cue.
All 17 Cuelume sounds are available:
chime, sparkle, droplet, bloom, whisper, tick, press, release,
toggle, success, error, page, loading, ready, pulse, scan, arrival.
Development
npm ci
npm test
npm packUse a local checkout
From the repository directory:
npm ci
npm run buildAdd the checkout's absolute directory path to plugins in your global or
project OpenCode configuration:
{
"$schema": "https://opencode.ai/config.json",
"plugins": [
{
"package": "/absolute/path/to/oc-sounds",
"options": { "volume": 0.85 }
}
]
}Rebuild after changing TypeScript source. Restart OpenCode's background service if it does not pick up changes to the local plugin.
Regenerate the sounds
The 17 mono, 48kHz PCM WAVs are checked into sounds/ and included in the package.
Normal builds and installs use these files directly. To regenerate them:
npm run sounds:renderThe renderer runs the unmodified Cuelume 0.2.2 engine through
node-web-audio-api's offline context, using seeded noise and preserving the
sound envelopes and shimmer tails. It requires no browser, Playwright, or audio
device. Cuelume and the native offline renderer are development dependencies;
neither is needed by the installed plugin.
Tests cover the V2 plugin lifecycle, event isolation/deduplication, concurrent tools, burst coalescing, playback spacing and priority, bounded queues, muting, cleanup, player fallback, and bundled WAV integrity. Scheduler tests use a fake clock; Linux player tests use fake executables and do not play audio.
Publish to npm
Review and test the package before publishing:
npm test
npm pack --dry-runThe prepack hook builds the plugin automatically. The package contains dist/,
sounds/, examples/, the README, and the license notices; development
dependencies and rendering scripts are excluded.
Sign in to an npm account with publishing access, then publish:
npm login
npm publish --access publicFor subsequent releases, commit your changes, bump the version, and publish:
npm version patch
npm publishUse minor or major instead of patch when appropriate. Each npm release needs
a new version number. After the first release, update the installation section
to remove the pending-release wording.
License
MIT. Cuelume sounds are by Daniel Belyi, also MIT. See THIRD_PARTY_NOTICES.md for attribution and license text.
