Files
Bose-SoundTouch/CLAUDE.md
T
Tobias GesellchenandClaude Opus 4.8 bd62fd6658 refactor: rename soundtouch-web to soundtouch-player (transitional alias) (refs #451)
The web player is intrinsically LAN-resident: it reaches speakers directly
and only delegates cloud-only features (e.g. TTS) to a possibly-remote
AfterTouch service via --service-url. That is exactly what a cloud-hosted
soundtouch-service cannot do, so the standalone player binary stays useful
and is not being deprecated. Rename it to state its purpose, with a
transition window so existing downloads keep working.

- cmd/soundtouch-web -> cmd/soundtouch-player; CLI name is now
  soundtouch-player. When the binary is invoked under its old name it prints
  a one-line rename notice (filepath.Base(os.Args[0])).
- Build/release both names from the same source: Makefile (build-player +
  build-web alias, dev-player* targets), Dockerfile (soundtouch-player image
  + transitional soundtouch-web image), release.yml and ci.yml (player +
  web artifacts, checksums, Docker images; release notes announce the
  rename). The soundtouch-web binary, image, and install script remain a
  transitional alias to be dropped in a future release (which will break
  stale fetch scripts and nudge users to the release notes).
- scripts/raspberry-pi/install-player.sh is canonical; install-web.sh keeps
  working but warns.
- Sweep docs, code comments, user-facing strings, and assets
  (soundtouch-web-ui.png, soundtouch-web-tunein.png, soundtouch-web-roadmap.md)
  to soundtouch-player; README documents the rename and why the player
  remains separate from the embedded /app.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 16:33:39 +02:00

245 lines
10 KiB
Markdown

# CLAUDE.md
Entry point for any Claude Code (or human) session working on this
repository. Read it before touching code.
## What this project is
Go library and toolset for controlling Bose SoundTouch speakers via
the local network API, plus a local cloud-service emulator. Bose
discontinued the SoundTouch cloud — this project keeps existing
speakers usable without it.
**Module:** `github.com/gesellix/bose-soundtouch`
Key binaries:
- `soundtouch-cli` — command-line control of one or more speakers
(status, play, presets, groups, migration, …).
- `soundtouch-service` — replacement for `streaming.bose.com`
and the `bmx` services, default port `8000`.
- `soundtouch-player` — Web UI for Radio browsing and device control.
- `soundtouch-backup` — Helper for on-device backup and restore.
Per-session pickup notes live in two local files at the repo root (they are `.gitignore`d and only exist if created during a session):
- `NEXT.md` — current "pick up here" log of open items.
- `DONE.md` — archive of recently resolved items.
## How a new session should start
1. Read this file.
2. Read `NEXT.md` if it's present — that's where running context lives.
3. Skim `README.md` for the user-facing pitch.
4. Skim `docs/` for the area you're touching. Long-form notes
(analysis, guides, troubleshooting) live there, not in the code.
5. Run `make check` once to confirm the local environment compiles,
vets, and tests cleanly.
## Build, test, run
```bash
# Build
make build # All binaries
make build-cli # Just CLI
make build-service # Just service
make build-web # Just web UI
make build-all # Cross-platform builds (Linux, macOS, Windows)
make install # Install to $GOPATH/bin
# Quality
make test # Unit tests
make test-coverage # Coverage reports
make check # fmt + vet + test
make lint # golangci-lint
make update-static-deps # Update frontend libraries (preact, htm) from node_modules
# Automation
A GitHub Action automatically runs `make update-static-deps` on Dependabot PRs that modify `package.json` to keep the vendored `.js` files in sync. Note: This requires `npm` to be installed.
# Development
make dev-service # Run local service on port 8000
make dev-discover # Discover devices on the LAN
make dev-info HOST=<ip> # Get device info
# Docker
make docker-build
make docker-run-host
```
**Pre-push quality gate:** `make lint` (golangci-lint) must be clean
before `git push`. CI runs it on every PR; running it locally first
saves a round-trip. `make check` covers `lint` is its own target —
combine as needed.
## Integration tests
The `.http` integration tests under `tests/integration/http-client/`
run via `make test-http-client`, which spins up the service plus
support mocks (`spotify-mock`, `amazon-mock`) using
`docker-compose.yml` + `docker-compose.ci.yml`, executes the suite
through the JetBrains HTTP client image, then tears the stack down.
Requires Docker.
The compose CI override mounts `tests/integration/testdata/` into the
service container as its persistent data dir. That directory is
listed in `tests/.gitignore` — it's local developer state, not source.
**Treat the testdata dir as debug evidence, not disposable scratch.**
When a fixture or schema change makes the old state stale (e.g.
post-anonymisation, the previous run's IPs no longer match the
assertions), don't `rm -rf` it — archive it:
```bash
make test-http-client-rotate # renames testdata/ → testdata_<timestamp>/
make test-http-client # fresh run on a clean slate
```
The rotate target is non-destructive (it moves, never deletes) and
opt-in (no other target invokes it). Old archives stay around for
retrospective diffing whenever something goes sideways.
## Project structure
```
cmd/
soundtouch-cli/ # CLI tool for device control
soundtouch-service/ # Local cloud service emulator
soundtouch-player/ # Web UI (TuneIn browser, device control)
soundtouch-backup/ # On-device backup helper
example-*/ # Usage examples
pkg/
client/ # HTTP + WebSocket client for the SoundTouch Web API
models/ # XML/JSON data structures
discovery/ # Device discovery (mDNS + UPnP, unified interface)
config/ # Configuration management
service/
bmx/ # Bose Media eXchange service emulation
marge/ # Device-management service emulation
handlers/ # HTTP request handlers (pkg/service/handlers/)
proxy/ # HTTP proxy with request recording
datastore/ # Persistent device data storage
certmanager/ # TLS certificate management
setup/ # Device migration and configuration
spotify/ # Spotify integration
stockholm/ # Optional Stockholm frontend bridge
soundtouchweb/ # SoundTouch Web UI service logic
examples/ # Feature demonstration programs
docs/ # Long-form analysis, guides, troubleshooting
.junie/ # Communication-style guidelines (see below)
```
## Key technologies
- **Go 1.26.3+**
- **chi v5** — HTTP router
- **gorilla/websocket** — WebSocket for real-time events
- **hashicorp/mdns** — mDNS device discovery
- **miekg/dns** — DNS operations and a custom DNS server
- **urfave/cli/v2** — CLI framework
## Architecture notes
- `pkg/client` is the core library for device API calls (HTTP + WebSocket).
- `pkg/service` is the local cloud replacement; routes wire to the
handlers in `pkg/service/handlers/` via chi middleware.
- Discovery supports both mDNS and UPnP/SSDP behind a unified interface.
- The SoundTouch Web API uses XML on the wire; internal service-to-service
messages use JSON.
- Tests cover unit, integration, parity (local vs. official Bose API
recordings), and regression. Reproducer tests should be refactored
into permanent regression or documentation tests rather than deleted.
## Load-bearing gotchas
### `ETag` header literal must stay capitalised
Bose speakers emit the response header with exact capitalisation
`ETag`. Go's `http.Header.Set` canonicalises to `Etag` (lowercase `t`).
Real speakers parse strictly — `Etag` is rejected. The codebase
deliberately bypasses the canonicalisation path; do **not** rewrite
the string literal `"ETag"` to `"Etag"` anywhere in `pkg/service/handlers/`
or in tests.
The contrast is encoded in two named constants in
`pkg/service/handlers/handlers_etag_test.go`:
```go
const normalizedEtag = "Etag" // what http.Header.Set produces
const caseSensitiveETag = "ETag" // what the speaker actually expects
```
Linter suppressions on the canonical-header check live alongside the
test code. Static-analysis warnings about `"ETag"` are expected;
don't "fix" them.
### Destructive git or filesystem actions need explicit confirmation
`git reset --hard`, `git checkout` that would overwrite local changes,
`git clean -fd`, `rm -rf` on non-build paths, `git stash drop` — all
should be proposed in writing with their consequences before running,
unless the user has already authorised that specific action in this
session. Prefer reversible alternatives (`git stash` over
`git reset --hard`).
**Force-flags also require explicit approval.** `git add -f` (force-add
a gitignored file), `git push --force`, `git push --force-with-lease`,
and any other flag that overrides a git safety mechanism must be
proposed and confirmed before running, for the same reason: they
bypass protections that exist intentionally.
## What never goes into this repo
This repository is public. The following must never be committed:
- **Real LAN IPs** of personal networks. Use RFC-5737 documentation
ranges in examples and fixtures: `192.0.2.0/24`, `198.51.100.0/24`,
`203.0.113.0/24`.
- **Real MAC addresses** or speaker device IDs from anyone's actual
hardware. Use `AA:BB:CC:DD:EE:FF` or `DEVICEID01` style placeholders.
- **Bose account IDs**, serial numbers, or tokens belonging to anyone
other than the committer's own test devices — and even those should
be sanitised before publication when feasible.
- **Bose firmware binaries, NAND dumps, or decompiled Bose code.**
- **Wi-Fi SSIDs or credentials**, captured or otherwise.
- **Network captures, traces, or logs** that include data from
accounts or devices other than your own test hardware.
- **Personal identifiers**: real names of speakers ("LivingRoom",
custom device names), private email addresses, household member
names visible in source IDs.
If you spot any of the above already in the tree, treat it as a
sanitisation task: stop, flag it to the maintainer, propose a
remediation commit before continuing.
## Disclaimers
"SoundTouch" and "Bose" are registered trademarks of Bose Corporation.
This project is an unofficial, community-built effort, not affiliated
with, endorsed by, or authorised by Bose.
## Communication style
When working with a human user in this repo:
- **Prioritise direct answers** to the question being asked, even when
it sits outside the current task or project context. Don't divert
back to whatever you were doing when the user asks something else.
- **Don't substitute assumptions for real information.** When something
is unclear, ask or check, rather than guessing and proceeding.
- **An issue is only "resolved" once the reporter confirms.** Prefer
"candidate fix, awaiting reporter confirmation" over "fixed" or
"closed" until the person who reported it says it works. A merged PR
or a shipped release is not confirmation.
- **Mind GitHub's `#<id>` auto-linking.** `#<id>` links to issues and
pull requests only — it does **not** resolve to discussions. For a
discussion, write the full URL
(`https://github.com/gesellix/Bose-SoundTouch/discussions/<id>`). For
security alerts, write e.g. "CodeQL alert 280" (no `#`) or the full
URL, since `#280` would point at an unrelated issue/PR.
These principles also apply to other AI assistants pointed at this
repo. Tool-specific config dirs (e.g. `.junie/`, `.claude/`) should
defer to this file as the source of truth instead of carrying their
own copies.