code-stream-overlay
v0.1.2
Published
Live, repo-aware overlays for OBS: git, tests, goals, timer and coding-agent activity.
Downloads
251
Maintainers
Readme
code-stream-overlay
Live, repo-aware overlays for OBS and other streaming software. Run it inside any git repository and add the printed URLs as browser sources. Viewers see what you're building, how far along it is, whether tests pass, and what your coding agent is doing.
- One command, zero config:
npx code-stream-overlay - Adds no files to your tracked tree; everything lives in
.git/code-stream-overlay/ - Every widget works as its own OBS browser source, plus one composed layout
Requires Node.js 22+ and git on your PATH.

Quick start
cd your-repo
npx code-stream-overlaycode-stream-overlay · my-repo · maven (passive)
Layout http://127.0.0.1:4747/
Widgets http://127.0.0.1:4747/w/<now|timer|git|tests|goals|commits|file|agent|custom>
Control http://127.0.0.1:4747/controlcode-stream-overlay init walks you through the options: test trigger mode, goals, Claude Code hooks and OBS sources. init --yes accepts the defaults.
Commands
| Command | What it does |
|---------|--------------|
| code-stream-overlay / start | Start the overlay server for this repo |
| init [--yes] | Setup wizard; writes .git/code-stream-overlay/config.json (or the shared code-stream-overlay.json) |
| pause · resume · next · stop | Control the running session; stop ends it and saves the summary |
| test | Trigger a test run |
| summary [--json] | Current session, or the last saved one |
| hooks install · hooks uninstall | Claude Code hooks |
| obs install · obs uninstall | OBS browser sources |
| uninstall [--purge] | Remove hooks and OBS sources; --purge also deletes .git/code-stream-overlay/ |
Global flags: --port, --host, --config <path>, --theme, --tone, --tests-mode, --no-open, --verbose, --trust-repo-config.
Ending a session (with stop, Ctrl+C or SIGTERM) prints a Markdown summary covering time, phases, lines changed, commits, test runs, goals and agent activity. The JSON version is saved in .git/code-stream-overlay/sessions/.
The control page (/control) has Pause / Resume / Next phase / Run tests / End session buttons. Add it to OBS as a custom browser dock.

Session timer
The timer widget counts down the current phase. Presets (session.preset):
| Preset | Phases |
|------------|------------------------------------------|
| 90min | Scope 10 · Build 60 · README 15 · Ship 5 |
| pomodoro | 4 × (Focus 25 · Break 5) |
| freeform | no phases, stopwatch |
Past the last phase the timer counts up in overtime. Custom phases:
{ "session": { "phases": [{ "name": "Build", "minutes": 45 }] } }.
Repo awareness
- now: title (config
title, else the GOALS.md heading, else the branch, else the repo name), subtitle and detected stack. - git: branch, lines added and removed, and files changed since the session started. Commits, staged, unstaged and untracked changes all count, so committing never resets the numbers.
- file: the file you're editing and how long ago.
Privacy
Everything shown on stream passes through one filter. Files that match privacy.ignore (default .env*, **/secrets/**, *.pem, *.key, id_*) show as "a hidden file", and their contents are never read. privacy.redactPaths: true shows basenames only, and privacy.hideFileNames: true hides every name. Commands and messages are scrubbed of passwords, tokens, SECRET=-style assignments, Authorization: headers and long key-like strings.
Tests
Tests are detected from your repo. The first match wins unless tests.adapter pins one ("off" disables tests).
| Stack | Detected by | Command | Default mode |
|--------|----------------------------------------------------|----------------------------------------------|--------------|
| maven | pom.xml | ./mvnw -q test or mvn -q test | passive |
| gradle | build.gradle(.kts), settings.gradle(.kts) | ./gradlew test or gradle test | passive |
| node | package.json with a test script | npm test (pnpm / yarn / bun by lockfile) | save |
| python | pyproject.toml, pytest.ini, setup.cfg, tox.ini | pytest -q --junitxml=.git/code-stream-overlay/pytest.xml | save |
| go | go.mod | go test -json ./... | save |
| rust | Cargo.toml | cargo nextest run if installed, else cargo test | save |
| dotnet | *.sln, *.csproj, *.fsproj | dotnet test --logger trx … | commit |
| make | Makefile with a test: target | make test | manual |
Trigger modes (tests.mode or --tests-mode):
- passive: never runs anything. It watches report files and shows the results of whatever ran the tests: you, your IDE, or your agent. Recommended whenever an agent runs tests itself, because two concurrent builds fight over
target/orbuild/. - save: runs after you save a file (debounced; one follow-up run is queued at most).
- commit: runs after each commit.
- interval: runs every
tests.intervalSecseconds (min 30). - manual: runs on
code-stream-overlay testorPOST /api/tests/run.
Override anything with tests.command, tests.reports (JUnit or TRX globs) and tests.timeoutSec. For example, jest with jest-junit:
{ "tests": { "command": "npx jest --ci --reporters=default --reporters=jest-junit", "reports": ["junit.xml"] } }Goals
Put a task list in GOALS.md (or set goals.file; .git/code-stream-overlay/goals.md keeps it private):
# CSV export for orders
- [x] Scope the feature
- [ ] Export endpoint
- [x] Header row
- [ ] Quote fields with commas
- [ ] Tests for edge cases
## Stretch
- [ ] Streaming for large exportsThe first # heading becomes the title. Top-level items count toward progress, and a parent counts only when its own box is checked. Items under a Stretch heading are dimmed and don't affect the percentage. Checking an item on stream strikes it through and slides it out.
goals.source can also be "claude-todos" (the coding agent's todo list), "both", or "off".
Coding agent
Claude Code
npm i -g code-stream-overlay # a stable path for the hook
code-stream-overlay hooks install # merges hooks into .claude/settings.local.jsonThe agent widget then shows what Claude is doing ("Editing src/Order.java", "Running ./mvnw test", "Waiting for you"), with counters for edits, commands and reads. Its task list can feed the goals widget (goals.source: "claude-todos" or "both").
The hook is built to be invisible. It never prints, always exits 0, takes about 50 ms, and gives up after 300 ms if the overlay isn't running. File contents and tool output never leave the hook. Prompts are never shown unless agent.showPrompts: true. code-stream-overlay hooks uninstall restores your settings file.
Any other agent
curl -X POST http://127.0.0.1:4747/api/agent -H "Authorization: Bearer $TOKEN" \
-d '{"type":"action","text":"Refactoring parser","tool":"aider"}'Event types: action (text, tool), thinking, waiting, done, todos ([{ "content": "...", "status": "in_progress" }]). The token is in .git/code-stream-overlay/server.json.
OBS
Automatic
OBS 28+ ships obs-websocket. Enable it under Tools → WebSocket Server Settings, then:
code-stream-overlay obs install # one browser source per widget, in the current scene
code-stream-overlay obs install --single # one full-canvas source for the composed layout
code-stream-overlay obs install --scene Coding
code-stream-overlay obs uninstall # removes exactly the sources it createdSources are named so · <widget> and positioned by their layout slot, scaled to your canvas. Running install again updates them without creating duplicates. The password is taken from --obs-password, then CODE_STREAM_OVERLAY_OBS_PASSWORD, then obs.password in .git/code-stream-overlay/config.json, and otherwise you're prompted.
Add the control page as a dock by hand: View → Docks → Custom Browser Docks → http://127.0.0.1:4747/control.
Manual
Sources → + → Browser, paste a URL from the start banner, and use the width and height from the table under Look and feel. Browser sources are transparent by default. Leave "Shutdown source when not visible" off so animations and reconnects keep working.
Look and feel
Layout. / is a 1920×1080 stage scaled to the source size. Widgets sit in slots, and widgets that share a slot stack:
| Widget | Default size | Default slot | |---------|--------------|---------------| | now | 900×90 | top-left | | timer | 360×120 | top-right | | git | 360×140 | right-middle | | tests | 360×140 | right-middle (below git) | | goals | 420×320 | left-middle | | agent | 1000×70 | bottom-center | | commits | 1920×48 | bottom-bar | | file | 600×200 | off by default | | custom | 360×80 | off by default |
Choose widgets with display.widgets. Move them with display.layout, either { "goals": { "slot": "bottom-left" } } or { "goals": { "x": 40, "y": 600, "w": 420, "h": 320 } }.
Themes. terminal (dark panels, monospace) and minimal (no panels, outlined text for any background). You can also give a path to your own .css file, which overrides the tokens from web/css/base.css (--so-fg, --so-bg, --so-accent, --so-pass, --so-fail, --so-radius, …). display.css appends extra CSS after the theme.
Tone. plain or playful ("All green, ship it", "3 gremlins loose"). Override any label with display.labels, e.g. { "tests.pass": "All green" }. Keys are listed in web/i18n/plain.json.
Per-source URL params change only that browser source:
| Param | Example | Effect |
|-------|---------|--------|
| theme | theme=minimal | theme for this source |
| tone | tone=playful | label set |
| font | font=Fira%20Code | any Google Fonts family |
| scale | scale=1.5 | 0.5 – 3 |
| align | align=right | left, center, right |
| bg | bg=solid | solid background instead of transparent |
| hide | hide=message,counts | hide sub-elements |
| label.<key> | label.tests.pass=Ship%20it | override one label |
| confetti | confetti=0 | no confetti |
Example: http://127.0.0.1:4747/w/tests?theme=neon&scale=1.5&label.tests.pass=Ship%20it
Animations. Tests going green flash and throw confetti (display.confetti: false turns that off), and tests going red shake. A checked goal strikes through and slides out, new commits slide into the ticker, a new timer phase is announced for 3 s, and the agent widget pulses while it waits for you. If the server goes away, widgets fade to 40% and recover on their own.
Configuration
Settings are merged in layers, later ones winning. Objects merge deeply and arrays replace.
- Built-in defaults
- Global:
~/.config/code-stream-overlay/config.json(%APPDATA%\code-stream-overlay\config.jsonon Windows) - Shared repo file:
code-stream-overlay.json(meant to be committed) - Private repo file:
.git/code-stream-overlay/config.json - CLI flags
- URL params (display only, per browser source)
{
"port": 4747,
"host": "127.0.0.1",
"title": null, // default: GOALS.md heading, then branch, then repo name
"subtitle": null,
"session": { "preset": "90min", "phases": null, "autoStart": true },
"tests": { "adapter": "auto", "command": null, "reports": null, "mode": null,
"debounceMs": null, "intervalSec": 300, "timeoutSec": 600 },
"goals": { "source": "file", "file": "GOALS.md", "stretchHeading": "Stretch" },
"agent": { "enabled": true, "idleAfterSec": 120, "showPrompts": false, "showCommands": true },
"privacy": { "ignore": [".env*", "**/secrets/**", "*.pem", "*.key", "id_*"],
"redactPaths": false, "hideFileNames": false },
"display": { "theme": "terminal", "tone": "plain", "font": null, "css": null, "labels": {},
"widgets": ["now", "timer", "git", "tests", "goals", "agent", "commits"],
"layout": {}, "confetti": true },
"obs": { "host": "127.0.0.1", "port": 4455, "password": null, "scene": null, "mode": "widgets" }
}Unknown keys and wrong types are errors, and the message names the file and the path.
Security
- The server binds to
127.0.0.1by default.--host 0.0.0.0prints a warning and requires the token on every route, and the printed URLs include it. - Write routes (
POST /api/*) always need the per-start token from.git/code-stream-overlay/server.json. - Requests whose
Hostheader isn't the server's own are rejected (DNS-rebinding guard). There are no CORS headers, and JSON bodies are capped at 64 KB. - The test command comes only from local config or the detected adapter, never from an HTTP request.
Files it writes
Everything lives in .git/code-stream-overlay/ (server.json, private config, session summaries, obs.json, test reports for pytest and .NET). The only exception is .claude/settings.local.json after hooks install, a local file that is excluded from git automatically. The only tracked files it writes are code-stream-overlay.json and GOALS.md, and only if you choose them in init.
Releasing (maintainers)
Releases are published by GitHub Actions (.github/workflows/release.yml) through npm trusted publishing, so no npm token lives in the repo.
npm version patch # bumps package.json + src/constants.ts, commits, tags vX.Y.Z
git push --follow-tags # the tag triggers the release workflowLicense
MIT
